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. Belasse aber alle dateien usw. innerhalb des arbeitsverzeichnisses der anwendung. Sorge dafür das für alle verwendeten grafikdateien eine fallbackmethode existiert, falls die grafikdatei nicht gefunden wird und erwähne dies in einem debug print. Nenne mir abschließend 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()