Diese anwendung hat ein eher bescheidenes gui bis jetzt. Doch anstat alles neu mit gui elementen etc zu erstellen hab ich etwas deutlich innovativeres vor. die gesamte anwendung konzentriert sich beereits auf das llm als mitelpunkt, daher will ich das auch alle innteraktionen die üblicherweise über gui elemente gesteuert und verändert werden, in dieser anwendung über konversation mit dem llm stattfindet. Dies sollte einfach un direkt sein und vorallem intuitiv. Das llm soll wisse was es bedeutet wenn man ihm zb sagt: "verwende den darkmode der anwendung", oder "sprache auf englisch", oder "erstelle den plan für:..."), "bearbeite dieses projekt", "zeige mir alle projekte die ich bereits erstellt habe", "beende die anwendung", "zeig mir die resultate von:" und so weiter und so weiter. Bereite alles sorgfältig wie diese art der umsetzung vor und dann beginne schritt für schritt mit der umsetzung davon um die anwendung bedienbar zu machen.

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

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(levelname)s - %(message)s',
                    handlers=[
                        RotatingFileHandler(os.path.join(BASE_DIR, "app.log"),
                                            maxBytes=5*1024*1024, backupCount=3, encoding='utf-8'),
                        logging.StreamHandler(sys.stdout)
                    ])

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

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

    def generate(self, model, prompt, temperature=0.7, max_tokens=150):
        key = (model, prompt, temperature, max_tokens)
        if key in self.cache:
            return self.cache[key]
        payload = {"model": model, "prompt": prompt, "temperature": temperature,
                   "max_tokens": max_tokens, "format": "json", "stream": False}
        headers = {"Content-Type": "application/json"}
        try:
            response = requests.post(f'{self.base_url}/api/generate',
                                     json=payload, headers=headers, timeout=60)
            if response.status_code == 200:
                result = response.json().get('response', '').strip()
                self.cache[key] = result
                return result
            logging.error(f"Error generating response: {response.status_code} - {response.text}")
            return f"Error generating response: {response.text}"
        except requests.RequestException as e:
            logging.error(f"Error communicating with LLM: {e}")
            return f"Error communicating with LLM: {e}"

    def add_to_memory(self, key, value):
        self.memory[key] = value

    def get_from_memory(self, key):
        return self.memory.get(key, '')

class TaskProcessor(QThread):
    progress_updated = pyqtSignal(int)
    task_message = pyqtSignal(str)
    task_result = pyqtSignal(str)
    task_status = pyqtSignal(int, int, str)

    def __init__(self, tasks, llm_client, llm_model, parent=None):
        super().__init__(parent)
        self.tasks = tasks
        self.llm_client = llm_client
        self.llm_model = llm_model
        self._paused = False
        self._stopped = False
        self.max_retries = 3
        self.context_memory = {}

    def run(self):
        total = sum(len(t.get('Goals', [])) for t in self.tasks)
        done = 0
        for ti, task in enumerate(self.tasks):
            if self._stopped: break
            self.task_message.emit(f"Assistant: Starting task '{task.get('Title', ti+1)}'")
            self.task_status.emit(ti, -1, 'processing')
            for gi, goal in enumerate(task.get('Goals', [])):
                if self._stopped: break
                retry = 0
                success = 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}")
                    self.task_message.emit(f"Assistant: Processing goal: {goal}")
                    self.task_status.emit(ti, gi, 'processing')
                    result = self.process_goal(task, goal, retry)
                    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(ti, gi, 'completed')
                    else:
                        self.task_status.emit(ti, gi, 'error')
                        retry +=1
                if not success:
                    self.task_message.emit(f"Assistant: Failed to process '{goal}' after {self.max_retries} attempts.")
                    self.task_status.emit(ti, gi, 'failed')
                done +=1
                self.progress_updated.emit(int(done / total * 100))
            self.task_status.emit(ti, -1, 'completed')
            summary = self.create_task_summary(task)
            self.task_result.emit(f"Summary for '{task.get('Title', ti+1)}':\n{summary}")
            self.context_memory[task.get('Title', ti+1)] = 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):
        context = self.get_relevant_context(goal)
        prompt = f"""
As an experienced software architect, create a solution for the goal "{goal}" based on:
**Description:** {task.get('ApplicationDescription', '')}
**Context:** {context}
        """
        return self.llm_client.generate(self.llm_model, prompt)

    def self_review(self, goal, result):
        prompt = f"""
Review the result for "{goal}":
{result}
Is it satisfactory? Yes or No.
        """
        review = self.llm_client.generate(self.llm_model, prompt)
        if "Yes" in review:
            return True
        self.task_message.emit(f"Assistant: Review failed for '{goal}': {review}")
        return False

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

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

    def generate_final_result(self):
        context = "\n".join(self.context_memory.values())
        prompt = f"""
Summarize all results and generate the complete application code with documentation based on:
{context}
        """
        return self.llm_client.generate(self.llm_model, prompt)

    def pause(self):
        self._paused = True

    def resume(self):
        self._paused = False

    def stop(self):
        self._stopped = True

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

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

    def init_ui(self):
        layout = QVBoxLayout()
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.setLineWidth(0)
        self.setStyleSheet("""
            QFrame {background-color: #2e2e2e; border-radius:10px; padding:10px;}
            QFrame[Sender='user'] {background-color: #0078d7;}
        """ if self.sender.lower() != "assistant" else """
            QFrame {background-color: #2e2e2e; border-radius:10px; padding:10px;}
        """)
        sender_label = QLabel(f"<b>{self.sender}:</b>")
        sender_label.setStyleSheet("color: #fff;")
        layout.addWidget(sender_label)
        parts = self.parse_message(self.message)
        for part in parts:
            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; font-size:12pt;}
                """)
                layout.addWidget(code)
        self.setLayout(layout)

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

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

    def init_ui(self):
        layout, llm_group, server_group, ui_group = QVBoxLayout(), QGroupBox("LLM Settings"), QGroupBox("Server Settings"), QGroupBox("UI 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.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)
        server_layout = QFormLayout()
        self.server_url = QLineEdit(self.settings.get('server_url','http://localhost'))
        self.server_port = QSpinBox(); self.server_port.setRange(1,65535); self.server_port.setValue(self.settings.get('server_port',11434))
        server_layout.addRow("Server URL:", self.server_url); server_layout.addRow("Server Port:", self.server_port)
        server_group.setLayout(server_layout)
        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))
        self.font_display = QLabel(f"{self.selected_font.family()}, {self.selected_font.pointSize()}pt, {'Bold' if self.selected_font.bold() else 'Regular'}")
        ui_layout.addRow("Theme:", self.theme_combo); ui_layout.addRow("Font:", self.font_button); ui_layout.addRow("", self.font_display)
        ui_group.setLayout(ui_layout)
        layout.addWidget(llm_group); layout.addWidget(server_group); layout.addWidget(ui_group)
        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
            self.font_display.setText(f"{font.family()}, {font.pointSize()}pt, {'Bold' if font.bold() else 'Regular'}")

    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.text(),
            'server_port': self.server_port.value(),
            'theme': self.theme_combo.currentText(),
            'font': self.selected_font
        })
        self.accept()

class ChatInputWidget(QWidget):
    send_message = pyqtSignal(str)
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QHBoxLayout()
        self.input = QTextEdit(); self.input.setFixedHeight(60)
        self.input.setStyleSheet("""
            QTextEdit {
                background-color: #2b2b2b; color: #dcdcdc;
                border: 1px solid #555; border-radius:4px;
                padding:5px; font-size:12pt;
            }
        """); self.input.setPlaceholderText("Type your message here...")
        self.send_btn = QPushButton("Send")
        self.send_btn.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_btn.clicked.connect(self.emit_msg)
        layout.addWidget(self.input); layout.addWidget(self.send_btn); layout.setContentsMargins(0,0,0,0)
        self.setLayout(layout)

    def emit_msg(self):
        msg = self.input.toPlainText().strip()
        if msg:
            self.send_message.emit(msg)
            self.input.clear()

class GanttChartWidget(QWidget):
    def __init__(self, plan, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(); 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, ax = Figure(figsize=(8,6)), None
            self.canvas = FigureCanvas(Figure(figsize=(8,6)))
            layout.addWidget(self.canvas); self.setLayout(layout); self.draw(plan)
        except ImportError:
            logging.error("Matplotlib not installed.")
            layout.addWidget(QLabel("Matplotlib not installed."))
            self.setLayout(layout)

    def draw(self, plan):
        if not plan:
            return
        self.figure.clear(); ax = self.figure.add_subplot(111)
        start = datetime.now(); durations = {t['Title']: max(len(t.get('Goals',[])),1) for t in plan}
        tasks, dur = list(durations.keys()), list(durations.values())
        starts = [start + timedelta(days=sum(dur[:i])) for i in range(len(dur))]
        for i, task in enumerate(tasks):
            ax.barh(i, 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()

    def update_chart(self, plan):
        self.draw(plan)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Intelligent Assistant for Developers')
        self.setGeometry(100,100,1800,1000)
        self.llm = LLMClient(); self.paused = self.stopped = False
        self.plan = []; self.files = []; self.server_proc = None
        self.settings = {
            'available_models': [], 'selected_model': '', 'temperature':0.7,
            'max_tokens':150, 'server_url':'http://localhost', 'server_port':11434,
            'theme':'Dark', 'font': QFont("Arial",10)
        }
        self.init_ui(); self.apply_styles(); self.update_server_status()

    def init_ui(self):
        self.create_menu(); self.create_toolbar(); self.status_bar = self.statusBar()
        self.status_bar.showMessage('Ready')
        self.stacked = QStackedWidget(); self.setCentralWidget(self.stacked)
        self.create_sidebar(); self.create_main_content(); self.stacked.setCurrentWidget(self.main_screen)

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

    def create_main_content(self):
        self.main_screen = QWidget(); layout = QHBoxLayout(); self.content_stack = QStackedWidget()
        self.chat_page = self.create_chat_page(); self.content_stack.addWidget(self.chat_page)
        self.plan_page = self.create_plan_page(); self.content_stack.addWidget(self.plan_page)
        self.files_page = self.create_files_page(); self.content_stack.addWidget(self.files_page)
        self.settings_page = self.create_settings_page(); self.content_stack.addWidget(self.settings_page)
        layout.addWidget(self.content_stack); self.main_screen.setLayout(layout); self.stacked.addWidget(self.main_screen)

    def switch_page(self, name):
        pages = {"Chat":self.chat_page, "Development Plan":self.plan_page,
                 "Files":self.files_page, "Settings":self.settings_page}
        self.content_stack.setCurrentWidget(pages.get(name, self.chat_page))
        for k, btn in self.nav_btns.items():
            btn.setChecked(k == name)

    def create_chat_page(self):
        chat = QWidget(); layout = QVBoxLayout()
        top = QHBoxLayout()
        llm_lbl = QLabel('Select LLM:'); llm_lbl.setStyleSheet("color:#fff; font-weight:bold; font-size:14pt;")
        self.llm_combo = QComboBox(); self.llm_combo.setStyleSheet("""
            QComboBox {background-color:#2b2b2b; color:#dcdcdc; border:1px solid #555; border-radius:4px; padding:5px; font-size:12pt;}
            QComboBox::drop-down {border:none;}
            QComboBox::down-arrow {image:url("icons/dropdown.png");}
        """)
        srv_lbl = QLabel('Server Status:'); srv_lbl.setStyleSheet("color:#fff; font-size:12pt;")
        self.srv_indicator = QLabel('⚪'); self.srv_indicator.setStyleSheet("color:#fff; font-size:16pt;")
        self.start_srv_btn = QPushButton('Start Server'); self.start_srv_btn.setStyleSheet("""
            QPushButton {background-color:#5bc0de; color:#fff; border:none; border-radius:5px; padding:8px; font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#31b0d5;}
        """); self.stop_srv_btn = QPushButton('Stop Server')
        self.stop_srv_btn.setStyleSheet("""
            QPushButton {background-color:#d9534f; color:#fff; border:none; border-radius:5px; padding:8px; font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#c9302c;}
        """); self.start_srv_btn.clicked.connect(self.start_server)
        self.stop_srv_btn.clicked.connect(self.stop_server)
        top.addWidget(llm_lbl); top.addWidget(self.llm_combo); top.addStretch()
        top.addWidget(srv_lbl); top.addWidget(self.srv_indicator)
        top.addWidget(self.start_srv_btn); top.addWidget(self.stop_srv_btn)
        self.chat_area = QScrollArea(); self.chat_area.setStyleSheet("""
            QScrollArea {background-color:#1e1e1e; border:none;}
            QScrollBar:vertical {background-color:#2b2b2b; width:12px;}
            QScrollBar::handle:vertical {background-color:#555; min-height:20px; border-radius:6px;}
            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {height:0px;}
            QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {background:none;}
        """); self.chat_container = QWidget(); self.chat_layout = QVBoxLayout()
        self.chat_layout.setAlignment(Qt.AlignTop); self.chat_container.setLayout(self.chat_layout)
        self.chat_area.setWidgetResizable(True); self.chat_area.setWidget(self.chat_container)
        self.progress = QProgressBar(); self.progress.setValue(0)
        self.progress.setAlignment(Qt.AlignCenter); self.progress.setStyleSheet("""
            QProgressBar {border:1px solid #555; border-radius:5px; text-align:center; height:20px;
                         background-color:#2b2b2b; color:#fff; font-size:12pt;}
            QProgressBar::chunk {background-color:#5bc0de; width:20px;}
        """)
        self.loading = QLabel(); self.loading.setAlignment(Qt.AlignCenter)
        gif = os.path.join(BASE_DIR, "loading.gif")
        if os.path.exists(gif):
            movie = QMovie(gif)
            if movie.isValid():
                self.loading.setMovie(movie); movie.start()
            else:
                self.loading.setText("Loading...")
        else:
            self.loading.setText("Loading...")
        self.loading.setVisible(False)
        self.chat_input = ChatInputWidget(); self.chat_input.send_message.connect(self.handle_msg)
        layout.addLayout(top); layout.addWidget(self.chat_area,1)
        layout.addWidget(self.chat_input); layout.addWidget(self.progress); layout.addWidget(self.loading)
        chat.setLayout(layout); return chat

    def create_plan_page(self):
        plan = QWidget(); layout = QVBoxLayout()
        self.plan_tree = QTreeWidget(); self.plan_tree.setHeaderLabels(['Task', 'Description/Goals'])
        self.plan_tree.setColumnWidth(0,300)
        self.plan_tree.setStyleSheet("""
            QTreeWidget {background-color:#2b2b2b; color:#dcdcdc; border:1px solid #555; border-radius:4px; font-size:12pt;}
            QTreeWidget::item:selected {background-color:#5bc0de; color:#fff;}
        """)
        self.analyse_btn = QPushButton('Start Analysis'); self.analyse_btn.setStyleSheet("""
            QPushButton {background-color:#f0ad4e; color:#fff; border:none; border-radius:5px; padding:10px; font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#ec971f;}
        """); self.start_proc_btn = QPushButton('Start Processing'); self.start_proc_btn.setStyleSheet("""
            QPushButton {background-color:#5cb85c; color:#fff; border:none; border-radius:5px; padding:10px; font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#4cae4c;}
        """); self.edit_plan_btn = QPushButton('Edit Plan'); self.edit_plan_btn.setStyleSheet("""
            QPushButton {background-color:#5bc0de; color:#fff; border:none; border-radius:5px; padding:10px; font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#31b0d5;}
        """)
        self.analyse_btn.setToolTip("Analyze the application description and create a development plan.")
        self.start_proc_btn.setToolTip("Start processing the development plan.")
        self.edit_plan_btn.setToolTip("Edit the current development plan.")
        btn_layout = QHBoxLayout(); btn_layout.addWidget(self.analyse_btn)
        btn_layout.addWidget(self.start_proc_btn); btn_layout.addWidget(self.edit_plan_btn)
        self.gantt = GanttChartWidget(self.plan); self.gantt.hide()
        self.toggle_gantt = QPushButton("Toggle Gantt Chart"); self.toggle_gantt.setStyleSheet("""
            QPushButton {background-color:#5bc0de; color:#fff; border:none; border-radius:5px; padding:8px; font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#31b0d5;}
        """); self.toggle_gantt.clicked.connect(self.toggle_gantt_chart)
        layout.addLayout(btn_layout); layout.addWidget(self.plan_tree)
        layout.addWidget(self.toggle_gantt); layout.addWidget(self.gantt); layout.addStretch()
        self.analyse_btn.clicked.connect(self.start_analysis)
        self.start_proc_btn.clicked.connect(self.start_processing)
        self.edit_plan_btn.clicked.connect(self.edit_plan)
        self.start_proc_btn.setEnabled(False); self.edit_plan_btn.setEnabled(False)
        plan.setLayout(layout); return plan

    def create_files_page(self):
        files = QWidget(); layout = QVBoxLayout()
        desc_lbl = QLabel('Application Description:'); desc_lbl.setStyleSheet("color:#fff; font-weight:bold; font-size:14pt;")
        self.desc_text = QTextEdit(); self.desc_text.setPlaceholderText("Enter the application description here...")
        self.desc_text.setStyleSheet("""
            QTextEdit {background-color:#2b2b2b; color:#dcdcdc; border:1px solid #555; border-radius:4px;
                      padding:5px; font-size:12pt;}
        """)
        self.upload_btn = QPushButton('Upload Files'); self.upload_btn.setStyleSheet("""
            QPushButton {background-color:#5bc0de; color:#fff; border:none; border-radius:5px; padding:10px; 
                         font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#31b0d5;}
        """); self.upload_btn.clicked.connect(self.upload_files)
        self.file_list = QListWidget(); self.file_list.setSelectionMode(QListWidget.ExtendedSelection)
        self.file_list.setContextMenuPolicy(Qt.CustomContextMenu); self.file_list.customContextMenuRequested.connect(self.file_menu)
        self.file_list.setStyleSheet("""
            QListWidget {background-color:#2b2b2b; color:#dcdcdc; border:1px solid #555; border-radius:4px;
                        padding:5px; font-size:12pt;}
            QListWidget::item:selected {background-color:#5bc0de; color:#fff;}
        """); self.file_list.setAcceptDrops(True)
        self.file_list.dragEnterEvent = self.drag_enter; self.file_list.dropEvent = self.drop_files
        self.manage_files_btn = QPushButton("Manage Files"); self.manage_files_btn.setStyleSheet("""
            QPushButton {background-color:#f0ad4e; color:#fff; border:none; border-radius:5px; padding:10px;
                         font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#ec971f;}
        """); self.manage_files_btn.clicked.connect(self.upload_files)
        self.manage_files_btn.setToolTip("Manage and upload project files.")
        layout.addWidget(desc_lbl); layout.addWidget(self.desc_text); layout.addWidget(self.upload_btn)
        layout.addWidget(QLabel('Uploaded Files:')); layout.addWidget(self.file_list)
        layout.addWidget(self.manage_files_btn); layout.addStretch(); files.setLayout(layout)
        return files

    def create_settings_page(self):
        settings = QWidget(); layout = QVBoxLayout()
        grp = QGroupBox('Settings'); grp.setStyleSheet("QGroupBox {color:#fff; font-weight:bold; font-size:14pt;}")
        frm = QFormLayout(); self.token_spin = QSpinBox(); self.token_spin.setRange(100,10000)
        self.token_spin.setValue(self.settings.get('max_tokens',150))
        frm.addRow('Max Tokens:', self.token_spin); grp.setLayout(frm)
        self.save_settings_btn = QPushButton("Save Settings"); self.save_settings_btn.setStyleSheet("""
            QPushButton {background-color:#5cb85c; color:#fff; border:none; border-radius:5px; padding:10px;
                         font-size:12pt; font-weight:bold;}
            QPushButton:hover {background-color:#4cae4c;}
        """); self.save_settings_btn.clicked.connect(self.save_settings)
        layout.addWidget(grp); layout.addWidget(self.save_settings_btn); layout.addStretch()
        settings.setLayout(layout); return settings

    def toggle_gantt_chart(self):
        if self.gantt.isVisible():
            self.gantt.hide()
        else:
            self.gantt.update_chart(self.plan); self.gantt.show()

    def create_menu(self):
        menu = self.menuBar()
        file_m = menu.addMenu('File')
        save_act = QAction('Save Project', self); save_act.setShortcut('Ctrl+S'); save_act.triggered.connect(self.save_project)
        load_act = QAction('Load Project', self); load_act.setShortcut('Ctrl+O'); load_act.triggered.connect(self.load_project)
        exit_act = QAction('Exit', self); exit_act.setShortcut('Ctrl+Q'); exit_act.triggered.connect(self.close)
        file_m.addActions([save_act, load_act, exit_act])
        edit_m = menu.addMenu('Edit')
        pause_act = QAction('Pause Processing', self); pause_act.setShortcut('Ctrl+P'); pause_act.triggered.connect(self.pause_processing)
        resume_act = QAction('Resume Processing', self); resume_act.setShortcut('Ctrl+R'); resume_act.triggered.connect(self.resume_processing)
        stop_act = QAction('Stop Processing', self); stop_act.setShortcut('Ctrl+T'); stop_act.triggered.connect(self.stop_processing)
        settings_act = QAction('Settings', self); settings_act.setShortcut('Ctrl+Shift+S'); settings_act.triggered.connect(self.open_settings_dialog)
        edit_m.addActions([pause_act, resume_act, stop_act, settings_act])
        help_m = menu.addMenu('Help')
        about_act = QAction('About', self); about_act.triggered.connect(self.show_about)
        help_m.addAction(about_act)

    def create_toolbar(self):
        toolbar = QToolBar('Tools'); self.addToolBar(Qt.TopToolBarArea, toolbar)
        def get_icon(path, fallback):
            full = os.path.join(BASE_DIR, path)
            return QIcon(full) if os.path.exists(full) else self.style().standardIcon(fallback)
        toolbar.addAction(QAction(get_icon("icons/start.png", QStyle.SP_MediaPlay), 'Start', self, triggered=self.start_processing))
        toolbar.addAction(QAction(get_icon("icons/pause.png", QStyle.SP_MediaPause), 'Pause', self, triggered=self.pause_processing))
        toolbar.addAction(QAction(get_icon("icons/resume.png", QStyle.SP_MediaSeekForward), 'Resume', self, triggered=self.resume_processing))
        toolbar.addAction(QAction(get_icon("icons/stop.png", QStyle.SP_MediaStop), 'Stop', self, triggered=self.stop_processing))

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

    def apply_styles(self):
        palette = QPalette()
        if self.settings['theme'] == 'Dark':
            palette.setColor(QPalette.Window, QColor(45,45,45))
            palette.setColor(QPalette.WindowText, Qt.white)
            palette.setColor(QPalette.Base, QColor(30,30,30))
            palette.setColor(QPalette.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)
        font = self.settings.get('font', QFont("Arial",10))
        self.setFont(font)
        for w in [self.desc_text, self.file_list, self.plan_tree, self.chat_input.input, self.chat_container]:
            w.setFont(font)

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

    def update_server_status(self):
        running = self.check_server()
        if running:
            self.srv_indicator.setText('🟢')
            self.start_srv_btn.setEnabled(False)
            self.stop_srv_btn.setEnabled(True)
            self.update_llm_list()
        else:
            self.srv_indicator.setText('🔴')
            self.start_srv_btn.setEnabled(True)
            self.stop_srv_btn.setEnabled(False)
            self.llm_combo.clear()
            self.analyse_btn.setEnabled(False)

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

    def start_server(self):
        if self.check_server():
            QMessageBox.information(self, "Info", "Server already running.")
            return
        try:
            cmd = 'ollama'
            path = shutil.which(cmd)
            if not path:
                QMessageBox.critical(self, "Error", f"Command '{cmd}' not found.")
                return
            self.server_proc = subprocess.Popen([cmd, 'serve', '--port', str(self.settings['server_port'])],
                                                stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True if os.name=='nt' else False)
            self.chat_area_add_message("System", "Starting LLM server...")
            self.server_timer = QTimer(); self.server_timer.timeout.connect(self.check_srv_ready)
            self.server_timer.start(1000); self.attempts = 0
        except Exception as e:
            logging.error(f"Start server error: {e}")
            QMessageBox.critical(self, "Error", f"Start server error: {e}")

    def check_srv_ready(self):
        self.attempts +=1
        if self.check_server():
            self.server_timer.stop(); self.update_server_status()
            self.chat_area_add_message("System", "LLM server started.")
        elif self.attempts >=15:
            self.server_timer.stop(); self.chat_area_add_message("System", "Server start timed out.")
            QMessageBox.warning(self, "Warning", "Server start timed out.")
            self.update_server_status()

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

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

    def display_files(self):
        self.file_list.clear(); self.file_list.addItems(self.files)

    def file_menu(self, pos):
        items = self.file_list.selectedItems()
        if not items: return
        menu = QMenu()
        remove = QAction("Remove Selected", self); remove.triggered.connect(self.remove_files)
        menu.addAction(remove); menu.exec_(self.file_list.mapToGlobal(pos))

    def remove_files(self):
        for item in self.file_list.selectedItems():
            path = item.text(); 
            if path in self.files: self.files.remove(path)
        self.display_files()

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

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

    def handle_dropped(self, files):
        new = [f for f in files if f not in self.files]
        self.files.extend(new); self.display_files()
        if new:
            self.chat_area_add_message("System", f"{len(new)} file(s) uploaded via drag-and-drop.")
        else:
            self.chat_area_add_message("System", "No new files uploaded via drag-and-drop.")

    def create_chat_page_content(self):
        pass

    def start_analysis(self):
        desc = self.desc_text.toPlainText().strip()
        if not desc:
            QMessageBox.warning(self, "Warning", "Enter application description first.")
            return
        temp, max_tok = self.settings.get('temperature',0.7), self.settings.get('max_tokens',150)
        self.chat_area_add_message("Assistant", "Starting analysis...")
        self.loading.setVisible(True); self.analyse_btn.setEnabled(False); threading.Thread(target=self.analyze, args=(desc,temp,max_tok), daemon=True).start()

    def analyze(self, desc, temp, max_tok):
        llm = self.llm_combo.currentText()
        if not llm:
            self.chat_area_add_message("Assistant", "No LLM selected.")
            self.loading.setVisible(False); self.analyse_btn.setEnabled(True)
            return
        files_content = self.read_files()
        prompt = f"""
As an experienced software architect, create a development plan based on:
**Description:** {desc}
**Files:** {files_content}
Provide only JSON with tasks, titles, descriptions, and goals.
        """
        plan_txt = self.llm.generate(llm, prompt, temp, max_tok)
        try:
            json_str = re.search(r'\[.*\]', plan_txt, re.DOTALL).group(0)
            plan = json.loads(json_str)
            if isinstance(plan, list) and all('Title' in t and 'Goals' in t for t in plan):
                self.plan = plan; self.display_plan()
                self.chat_area_add_message("Assistant", "Analysis completed.")
                self.start_proc_btn.setEnabled(True); self.edit_plan_btn.setEnabled(True)
            else:
                self.chat_area_add_message("Assistant", "Invalid plan structure.")
        except:
            self.chat_area_add_message("Assistant", "Failed to parse plan.")
        self.loading.setVisible(False); self.analyse_btn.setEnabled(True)

    def read_files(self):
        content = ""
        for f in self.files:
            try:
                if os.path.splitext(f)[1].lower() in ['.txt','.py','.md','.json','.yaml','.yml']:
                    with open(f, 'r', encoding='utf-8') as file:
                        content += f"\nContent of {os.path.basename(f)}:\n{file.read()}"
            except:
                pass
        return content

    def display_plan(self):
        self.plan_tree.clear()
        for ti, task in enumerate(self.plan):
            ti_text = task.get('Title', f'Task {ti+1}')
            ti_desc = task.get('Description','')
            ti_item = QTreeWidgetItem([ti_text]); ti_item.setData(0, Qt.UserRole, (ti,None))
            ti_item.setBackground(0, QColor('#444'))
            if ti_desc:
                desc_item = QTreeWidgetItem(['Description', ti_desc])
                ti_item.addChild(desc_item)
            goals = QTreeWidgetItem(['Goals'])
            for gi, goal in enumerate(task.get('Goals', [])):
                goal_item = QTreeWidgetItem([goal]); goal_item.setData(0, Qt.UserRole, (ti, gi))
                goal_item.setBackground(0, QColor('#555')); goals.addChild(goal_item)
            ti_item.addChild(goals); self.plan_tree.addTopLevelItem(ti_item)
        self.gantt.update_chart(self.plan)

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

    def start_processing(self):
        if not self.plan:
            QMessageBox.warning(self, "Warning", "Create a development plan first.")
            return
        if not self.llm_combo.currentText():
            QMessageBox.warning(self, "Warning", "Select an LLM.")
            return
        self.chat_area_add_message("Assistant", "Starting task processing.")
        self.loading.setVisible(True); self.start_proc_btn.setEnabled(False); self.edit_plan_btn.setEnabled(False); self.analyse_btn.setEnabled(False)
        self.processor = TaskProcessor(self.plan, self.llm, self.llm_combo.currentText())
        self.processor.progress_updated.connect(self.update_progress)
        self.processor.task_message.connect(self.chat_area_add_message)
        self.processor.task_result.connect(self.chat_area_add_message)
        self.processor.task_status.connect(self.update_task_status)
        self.processor.finished.connect(self.proc_finished)
        self.processor.start()

    def proc_finished(self):
        self.loading.setVisible(False); self.start_proc_btn.setEnabled(True); self.edit_plan_btn.setEnabled(True)
        self.analyse_btn.setEnabled(True); self.chat_area_add_message("Assistant", "Processing finished.")

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

    def update_progress(self, val):
        self.progress.setValue(val); self.status_bar.showMessage(f'Progress: {val}%')

    def update_task_status(self, ti, gi, status):
        color = {'processing': QColor('#FFD700'), 'completed': QColor('#32CD32'),
                 'error': QColor('#FF4500'), 'failed': QColor('#DC143C')}.get(status, QColor('#555'))
        if ti < self.plan_tree.topLevelItemCount():
            task = self.plan_tree.topLevelItem(ti)
            if gi == -1:
                task.setBackground(0, color)
            else:
                goals = task.child(1)
                if goals and gi < goals.childCount():
                    goals.child(gi).setBackground(0, color)

    def pause_processing(self):
        if hasattr(self, 'processor') and self.processor.isRunning():
            self.processor.pause(); self.chat_area_add_message("System", "Processing paused.")

    def resume_processing(self):
        if hasattr(self, 'processor') and self.processor.isRunning():
            self.processor.resume(); self.chat_area_add_message("System", "Processing resumed.")

    def stop_processing(self):
        if hasattr(self, 'processor') and self.processor.isRunning():
            self.processor.stop(); self.processor.wait()
            self.chat_area_add_message("System", "Processing stopped."); self.progress.setValue(0); self.loading.setVisible(False)
            self.start_proc_btn.setEnabled(True); self.edit_plan_btn.setEnabled(True); self.analyse_btn.setEnabled(True)
        else:
            QMessageBox.information(self, "Info", "No ongoing process to stop.")

    def save_project(self):
        file, _ = QFileDialog.getSaveFileName(self, "Save Project", "", "JSON Files (*.json)")
        if file:
            data = {
                'description': self.desc_text.toPlainText(),
                'uploaded_files': self.files,
                'plan_data': self.plan,
                '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': {'family': self.settings['font'].family(),
                             'pointSize': self.settings['font'].pointSize(),
                             'bold': self.settings['font'].bold(),
                             'italic': self.settings['font'].italic(),
                             'underline': self.settings['font'].underline(),
                             'strikeOut': self.settings['font'].strikeOut(),
                             'weight': self.settings['font'].weight(),
                             'style': self.settings['font'].style(),
                             'styleHint': self.settings['font'].styleHint()}
                }
            }
            try:
                with open(file, 'w', encoding='utf-8') as f:
                    json.dump(data, f, ensure_ascii=False, indent=4)
                QMessageBox.information(self, "Success", "Project saved successfully."); logging.debug(f"Project saved: {file}")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Save error: {e}"); logging.error(f"Save error: {e}")

    def load_project(self):
        file, _ = QFileDialog.getOpenFileName(self, "Load Project", "", "JSON Files (*.json)")
        if file:
            try:
                with open(file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                self.desc_text.setPlainText(data.get('description','')); self.files = data.get('uploaded_files',[]); self.display_files()
                self.plan = data.get('plan_data',[]); self.display_plan()
                settings = data.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 = QFont(settings.get('font',{}).get('family','Arial'),
                                settings.get('font',{}).get('pointSize',10))
                    font.setBold(settings.get('font',{}).get('bold',False))
                    font.setItalic(settings.get('font',{}).get('italic',False))
                    font.setUnderline(settings.get('font',{}).get('underline',False))
                    font.setStrikeOut(settings.get('font',{}).get('strikeOut',False))
                    font.setWeight(settings.get('font',{}).get('weight',QFont.Normal))
                    font.setStyle(settings.get('font',{}).get('style',QFont.StyleNormal))
                    font.setStyleHint(settings.get('font',{}).get('styleHint',QFont.AnyStyle))
                    self.settings['font'] = font
                self.apply_styles(); self.switch_page("Development Plan")
                QMessageBox.information(self, "Success", "Project loaded successfully."); logging.debug(f"Project loaded: {file}")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Load error: {e}"); logging.error(f"Load error: {e}")

    def show_about(self):
        QMessageBox.about(self, "About", "Intelligent Assistant for Developers\nVersion 2.6\n\nAssists developers in creating development plans and scripts using LLMs.")

    def handle_msg(self, msg):
        self.chat_area_add_message("User", msg)
        reply = self.llm.generate(self.settings['selected_model'], msg, self.settings['temperature'], self.settings['max_tokens'])
        self.chat_area_add_message("Assistant", reply)

    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Exit', 'Are you sure you want to exit?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.server_proc and self.server_proc.poll() is None:
                try:
                    self.server_proc.terminate(); self.server_proc.wait(timeout=10)
                except:
                    self.server_proc.kill()
            if hasattr(self, 'processor') and self.processor.isRunning():
                self.processor.stop(); self.processor.wait()
            event.accept()
        else:
            event.ignore()

    def new_project(self):
        self.desc_text.clear(); self.files=[]; self.display_files(); self.plan=[]; self.plan_tree.clear(); self.gantt.update_chart(self.plan)
        self.start_proc_btn.setEnabled(False); self.edit_plan_btn.setEnabled(False); self.stacked.setCurrentWidget(self.main_screen)
        self.chat_area_add_message("System", "Started a new project.")

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

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

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

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

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

    def handle_project_save(self):
        pass

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