diese anwendung soll auf eine innovative art und weise funktionieren, sowohl technisch, wie auch beim verwenden selbst. Um diese anwendung intuitiver für den nutzer zu gestalten und auch auf individuelle bedürfnisse und vorkenntnisse der nutzer besser eingehen zu können, soll die anwendung, nicht wie gewöhnliche anwendungen durch das verwenden von gui elementen, sondern über eine konversation, beziehungsweise über das chatten mit der anwendung selbst und dem llm geschehen. Das Gui soll vorallem für anzeigen von informationen und veranschaulichung dienen, aber soweit es sinnvoll und möglich ist soll die verwendung von funktionen, das ändern von einstellungen, das starten von prozessen und so weiter alles über den llm chat durchgeführt werden. Der nutzer soll natürliche sprache im chat verwenden können, um alle bereiche der anwendung steuern zu können. Damit sind alle bereiche gemeint, die sonst üblicherweise über gui elemente genutzt werden würde, wie zb buttons oder eingabefelder und so weiter. Nutze diese vorgaben um ein gut funktionierendes system zu entwickeln mit dem diese anwendung vollständig, innovativ, intuitiv und nutzerfreundlich verwendet werden kann. Erstelle die dafür notwendigen codes und passe das gui entsprechend an. Dafür könnten große veränderungen notwendig sein und eine völlig neue art von gui entwickelt werden. Arbeite daher besonders sorgfälltig und vorrausschauend. Es wurde in diesem skript auch bereits mit der umsetzung begonnen. Erstelle mir am ende der nachricht das bearbeitete, vollständige skript mit dem gesamten code der anwendung.


import sys
import threading
import requests
import json
import logging
import subprocess
import os
import re
import shutil
from datetime import datetime, timedelta
from logging.handlers import RotatingFileHandler
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QLabel, QPushButton, QAction, QHBoxLayout, QVBoxLayout,
    QWidget, QTextEdit, QFileDialog, 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.rstrip('/')
        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, prompt, temperature=0.7, max_tokens=150):
        cache_key = (prompt, temperature, max_tokens)
        if cache_key in self.cache:
            logging.debug("Using cached result.")
            return self.cache[cache_key]
        payload = {
            "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 CommandDispatcher:
    def __init__(self, main_window, llm_client):
        self.main_window = main_window
        self.llm_client = llm_client

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

        logging.info(f"Dispatching action: {action} with params: {params}")

        actions = {
            'start_server': self.main_window.start_server,
            'stop_server': self.main_window.stop_server,
            '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
            # Add more actions as needed
        }

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

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(prompt, temperature=0.7, max_tokens=150)

    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(prompt, temperature=0.5, max_tokens=10)
        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(prompt, temperature=0.7, max_tokens=500)

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

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

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

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

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

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

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

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

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Intelligent Assistant for Developers')
        self.setGeometry(100, 100, 800, 600)
        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.llm_client = LLMClient(base_url=f"{self.settings['server_url']}:{self.settings['server_port']}")
        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.command_dispatcher = CommandDispatcher(self, self.llm_client)

    def init_ui(self):
        central_widget = QWidget()
        main_layout = QVBoxLayout()
        
        # 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)

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

        main_layout.addWidget(self.chat_area, stretch=1)
        main_layout.addWidget(self.loading_label)
        main_layout.addWidget(self.progress_bar)
        main_layout.addWidget(self.chat_input)
        main_layout.setStretch(0, 1)
        main_layout.setStretch(1, 0)
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

        self.create_menu()

    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_command),
                                       ('Load Project', 'Ctrl+O', self.load_project_command),
                                       ('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_command),
                                       ('Resume Processing', 'Ctrl+R', self.resume_processing_command),
                                       ('Stop Processing', 'Ctrl+T', self.stop_processing_command),
                                       ('Settings', 'Ctrl+Shift+S', self.open_settings_dialog_command)]:
            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 handle_user_message(self, message):
        self.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 a prompt that instructs the LLM to return either a reply or a command in JSON
        prompt = f"""
You are an intelligent assistant that helps users manage their development projects. 
You can perform 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.

When a user message requires performing an action, respond with a JSON object:
{{"action": "action_name", "params": {{...}}}}

Otherwise, respond with a helpful message.

User message: "{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)
            else:
                # If not a command, treat as a regular message
                self.chat_area_add_message("Assistant", response)
        except json.JSONDecodeError:
            # If parsing fails, treat as a regular message
            self.chat_area_add_message("Assistant", response)

    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 start_server_command(self, **kwargs):
        self.start_server()

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

    def upload_files_command(self, **kwargs):
        # Expecting 'files' parameter as list of file paths
        files = kwargs.get('files', [])
        if files:
            self.uploaded_files.extend(files)
            self.display_uploaded_files()
            self.chat_area_add_message("System", f"{len(files)} file(s) uploaded via command.")
            logging.debug(f"Uploaded files via command: {files}")
        else:
            self.chat_area_add_message("System", "No files provided for upload.")

    def remove_selected_files_command(self, **kwargs):
        # Expecting 'files' parameter as 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.")
            logging.debug(f"Removed files via command: {removed}")
        else:
            self.chat_area_add_message("System", "No files provided for removal.")

    def change_setting(self, **kwargs):
        # Expecting '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.base_url = f"{self.settings['server_url'].rstrip('/')}" \
                                               f":{self.settings['server_port']}"
                self.apply_styles()
                self.chat_area_add_message("System", f"Setting '{setting}' changed to '{value}'.")
                logging.debug(f"Setting '{setting}' changed to '{value}'.")
            else:
                self.chat_area_add_message("System", f"Setting '{setting}' does not exist.")
                logging.warning(f"Attempted to change non-existent setting '{setting}'.")
        else:
            self.chat_area_add_message("System", "Invalid parameters 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 start_analysis_command(self, **kwargs):
        self.start_analysis()

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

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

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

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

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

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

    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]:
            widget.setFont(self.settings.get('font', QFont("Arial", 10)))

    def update_llm_list(self):
        models = self.llm_client.get_models()
        self.settings['available_models'] = models
        self.settings['selected_model'] = models[0] if models else ''
        if models:
            logging.debug(f"Available models updated: {models}")
            self.chat_area_add_message("System", f"Available models: {', '.join(models)}. Selected model: {self.settings['selected_model']}.")
        else:
            logging.warning("No models found.")
            self.chat_area_add_message("System", "No models found on the LLM server.")

    def update_server_status(self):
        running = self.check_server_running()
        if running:
            self.chat_area_add_message("System", "Server Status: 🟢 Running")
            self.update_llm_list()
        else:
            self.chat_area_add_message("System", "Server Status: 🔴 Stopped")
            self.settings['available_models'] = []
            self.settings['selected_model'] = ''
    
    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.")
            logging.info("Start server attempted, but already running.")
            return
        try:
            llm_cmd = 'ollama'
            server_path = shutil.which(llm_cmd)
            if not server_path:
                self.chat_area_add_message("System", 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:
            self.chat_area_add_message("System", f"Failed to start server: {e}")
            logging.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.")
            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:
                self.chat_area_add_message("System", f"Failed to stop server: {e}")
                logging.error(f"Error stopping server: {e}")
        else:
            self.chat_area_add_message("System", "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.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 upload_files_command(self, **kwargs):
        # Handle file uploads via command with 'files' param
        files = kwargs.get('files', [])
        if not files:
            self.chat_area_add_message("System", "No files provided for upload.")
            return
        # For security reasons, ensure that file paths are valid and accessible
        accessible_files = [f for f in files if os.path.isfile(f)]
        if accessible_files:
            self.uploaded_files.extend(accessible_files)
            logging.debug(f"Uploaded files via command: {accessible_files}")
            self.chat_area_add_message("System", f"{len(accessible_files)} file(s) uploaded via command.")
        else:
            self.chat_area_add_message("System", "No valid files provided for upload.")

    def remove_selected_files(self):
        # Find and remove files based on command
        # This will be handled via command, so no direct GUI interaction
        pass  # Placeholder, handled via remove_selected_files_command

    def remove_selected_files_command(self, **kwargs):
        files = kwargs.get('files', [])
        if not files:
            self.chat_area_add_message("System", "No files provided for removal.")
            return
        removed = [f for f in files if f in self.uploaded_files]
        if removed:
            for f in removed:
                self.uploaded_files.remove(f)
            self.chat_area_add_message("System", f"{len(removed)} file(s) removed via command.")
            logging.debug(f"Removed files via command: {removed}")
        else:
            self.chat_area_add_message("System", "No matching files found to remove.")

    def display_uploaded_files(self):
        # Since GUI is minimized, inform user via chat
        self.chat_area_add_message("System", f"Uploaded Files:\n" + "\n".join(self.uploaded_files))

    def change_setting(self, **kwargs):
        # Handled in dispatcher's change_setting
        pass

    def save_project(self):
        # Save project via dialog
        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)
                self.chat_area_add_message("System", "Project saved successfully.")
                logging.debug(f"Project saved to {file}.")
            except Exception as e:
                self.chat_area_add_message("System", f"Failed to save project: {e}")
                logging.error(f"Failed to save project: {e}")

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

    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)
                # Assuming 'description' is part of project
                self.description_text = QMessageBox.question(self, "Load Description", "Do you want to load the project description?", QMessageBox.Yes | QMessageBox.No)
                if self.description_text == QMessageBox.Yes:
                    description = project.get('description', '')
                    self.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.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.llm_client.base_url = f"{self.settings['server_url'].rstrip('/')}" \
                                               f":{self.settings['server_port']}"
                    self.apply_styles()
                if self.plan_data:
                    self.chat_area_add_message("System", "Loaded Development Plan.")
                    logging.debug("Development plan loaded.")
                QMessageBox.information(self, "Success", "Project loaded successfully.")
                logging.debug(f"Project loaded from {file}.")
            except Exception as e:
                self.chat_area_add_message("System", f"Failed to load project: {e}")
                logging.error(f"Failed to load project: {e}")

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

    def new_project(self):
        self.uploaded_files = []
        self.plan_data = []
        self.chat_area_add_message("System", "Started a new project.")
        logging.debug("New project initiated.")

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

    def start_analysis_command(self, **kwargs):
        # Start analysis via command
        # Expecting 'description' and optionally 'files'
        description = kwargs.get('description')
        if not description:
            self.chat_area_add_message("System", "No description provided for analysis.")
            return
        self.task_processor = TaskProcessor(self.plan_data, self.llm_client, self.settings['selected_model'])
        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()
        self.chat_area_add_message("System", "Started analysis of the project.")
        logging.debug("TaskProcessor started for analysis.")

    def start_analysis(self):
        # This function would typically gather description and start analysis
        QMessageBox.information(self, "Info", "Analysis can be started via chat commands.")
        logging.info("Start analysis initiated via command.")

    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. Start analysis first.")
            logging.warning("Processing started without a plan.")
            return
        if not self.settings.get('selected_model'):
            self.chat_area_add_message("System", "No LLM selected. Please set the model via settings.")
            logging.warning("Processing started without LLM selection.")
            return
        self.chat_area_add_message("System", "Starting task processing...")
        self.task_processor = TaskProcessor(self.plan_data, self.llm_client, self.settings['selected_model'])
        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 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.")
            logging.debug("Processing paused.")
        else:
            self.chat_area_add_message("System", "No ongoing process to pause.")
            logging.info("Pause processing attempted with no active process.")

    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.")
            logging.debug("Processing resumed.")
        else:
            self.chat_area_add_message("System", "No paused process to resume.")
            logging.info("Resume processing attempted with no active process.")

    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.progress_bar.setValue(0)
            self.loading_label.setVisible(False)
            logging.debug("Processing stopped.")
        else:
            self.chat_area_add_message("System", "No ongoing process to stop.")
            logging.info("Stop processing attempted with no active process.")

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

    def toggle_gantt_chart(self):
        # Since GUI is minimized, inform user to use chat command
        self.chat_area_add_message("System", "Gantt chart is currently not displayed in the GUI.")
        logging.info("Gantt chart toggle attempted via command.")

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

    def generate_final_report(self):
        if hasattr(self, 'task_processor') and hasattr(self.task_processor, 'generate_final_result'):
            final = self.task_processor.generate_final_result()
            self.chat_area_add_message("Assistant", final)
        else:
            self.chat_area_add_message("System", "No final report available. Start processing tasks first.")
            logging.warning("Generate final report attempted without active TaskProcessor.")

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

    def open_settings_dialog_command(self, **kwargs):
        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('/')}" \
                                       f":{self.settings['server_port']}"
            self.update_server_status()
            self.chat_area_add_message("System", "Settings updated successfully.")
            self.update_llm_list()

    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 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.uploaded_files = []
        self.plan_data = []
        self.chat_area_add_message("System", "Started a new project.")
        logging.debug("New project initiated.")

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

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

    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

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

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

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

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

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

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

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

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

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