die anwendung scheint nicht korrekt zu prüfen wie der status vom ollama server ist und ob bereits vor start der anwendung ein ollama seerver ausgeführt wird. Auch eine abfrage welche llms installiert sind scheint nicht zu klappen. Genau so funktioniert dass starten des ollama servers nicht. Behebe all diese probleme sorgfälltig. Hier sind noch logs einer ausführung:
2024-10-09 01:44:17,904 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:44:17,904 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:44:21,992 - ERROR - Error fetching models: HTTPConnectionPool(host='localhost', port=11434): Max retries exceeded with url: /api/models (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021FAB8F4220>: Failed to establish a new connection: [WinError 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte'))
2024-10-09 01:44:21,992 - ERROR - Error checking server status: HTTPConnectionPool(host='localhost', port=11434): Max retries exceeded with url: /api/models (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021FAB8F4610>: Failed to establish a new connection: [WinError 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte'))
2024-10-09 01:44:21,992 - DEBUG - Found models: []
2024-10-09 01:44:21,992 - WARNING - No models found.
2024-10-09 01:44:22,221 - DEBUG - Server status updated: Stopped
2024-10-09 01:44:26,998 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:44:31,067 - ERROR - Error checking server status: HTTPConnectionPool(host='localhost', port=11434): Max retries exceeded with url: /api/models (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021FAB8BB400>: Failed to establish a new connection: [WinError 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte'))
2024-10-09 01:44:31,067 - DEBUG - Server status updated: Stopped
2024-10-09 01:44:36,078 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:44:40,136 - ERROR - Error checking server status: HTTPConnectionPool(host='localhost', port=11434): Max retries exceeded with url: /api/models (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021FAB8F4310>: Failed to establish a new connection: [WinError 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte'))
2024-10-09 01:44:40,136 - DEBUG - Server status updated: Stopped
2024-10-09 01:44:45,151 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:44:47,196 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:44:47,197 - DEBUG - Server responded with status code: 404
2024-10-09 01:44:47,197 - DEBUG - Server status updated: Stopped
2024-10-09 01:44:52,197 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:44:54,234 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:44:54,235 - DEBUG - Server responded with status code: 404
2024-10-09 01:44:54,235 - DEBUG - Server status updated: Stopped
2024-10-09 01:44:59,247 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:01,271 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:01,271 - DEBUG - Server responded with status code: 404
2024-10-09 01:45:01,272 - DEBUG - Server status updated: Stopped
2024-10-09 01:45:03,261 - DEBUG - Attempting to start the Ollama server...
2024-10-09 01:45:06,279 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:08,289 - DEBUG - Server status updated: Running
2024-10-09 01:45:08,289 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:08,306 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:08,307 - DEBUG - Server responded with status code: 404
2024-10-09 01:45:10,326 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:10,327 - ERROR - Error fetching models: 404 - 404 page not found
2024-10-09 01:45:10,327 - DEBUG - Found models: []
2024-10-09 01:45:10,327 - WARNING - No models found.
2024-10-09 01:45:10,337 - DEBUG - Server status updated: Stopped
2024-10-09 01:45:13,317 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:15,357 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:15,358 - DEBUG - Server responded with status code: 404
2024-10-09 01:45:15,358 - DEBUG - Server status updated: Stopped
2024-10-09 01:45:20,372 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:22,427 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:22,428 - DEBUG - Server responded with status code: 404
2024-10-09 01:45:22,428 - DEBUG - Server status updated: Stopped
2024-10-09 01:45:27,435 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:29,460 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:29,462 - DEBUG - Server responded with status code: 404
2024-10-09 01:45:29,462 - DEBUG - Server status updated: Stopped
2024-10-09 01:45:34,462 - DEBUG - Starting new HTTP connection (1): localhost:11434
2024-10-09 01:45:36,481 - DEBUG - http://localhost:11434 "GET /api/models HTTP/1.1" 404 18
2024-10-09 01:45:36,482 - DEBUG - Server responded with status code: 404
2024-10-09 01:45:41,502 - DEBUG - Server status updated: Stopped


Skript:

import sys
import threading
import requests
import json
import logging
import time
import subprocess
import os
from datetime import datetime
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QLabel, QPushButton, QComboBox, QAction,
    QHBoxLayout, QVBoxLayout, QWidget, QTextEdit, QFileDialog,
    QTreeWidget, QTreeWidgetItem, QTextBrowser, QProgressBar, QTabWidget,
    QListWidget, QStatusBar, QMessageBox, QInputDialog, QLineEdit, QGroupBox, QFormLayout, QSpinBox
)
from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal
from PyQt5.QtGui import QColor

# Configure logging to output to both console and file
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("app.log", mode='w'),
        logging.StreamHandler()
    ]
)


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

    def get_models(self):
        try:
            response = requests.get(f'{self.base_url}/api/models')
            if response.status_code == 200:
                models = response.json()
                logging.debug(f"Models response: {models}")
                if isinstance(models, list):
                    if models and isinstance(models[0], dict) and 'name' in models[0]:
                        model_names = [model['name'] for model in models]
                    elif all(isinstance(model, str) for model in models):
                        model_names = models
                    else:
                        logging.error(f"Unexpected models format: {models}")
                        return []
                else:
                    logging.error(f"Unexpected models format: {models}")
                    return []
                return model_names
            else:
                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):
        cache_key = (model, prompt)
        if cache_key in self.cache:
            return self.cache[cache_key]
        payload = {
            "model": model,
            "prompt": prompt,
            "stream": False  # Disable streaming to simplify response handling
        }
        headers = {"Content-Type": "application/json"}
        try:
            response = requests.post(f'{self.base_url}/api/generate', json=payload, headers=headers)
            if response.status_code == 200:
                json_response = response.json()
                logging.debug(f"Generation response: {json_response}")
                if isinstance(json_response, dict):
                    result = json_response.get('response', '')
                elif isinstance(json_response, list):
                    result = ''.join([item.get('response', '') for item in json_response])
                else:
                    result = ''
                self.cache[cache_key] = result.strip()
                return result.strip()
            else:
                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 ServerStatusChecker(QThread):
    status_updated = pyqtSignal(bool)

    def __init__(self, base_url, parent=None):
        super().__init__(parent)
        self.base_url = base_url
        self._running = True

    def run(self):
        while self._running:
            try:
                response = requests.get(f'{self.base_url}/api/models', timeout=5)
                is_running = response.status_code == 200
                logging.debug(f"Server responded with status code: {response.status_code}")
            except requests.RequestException as e:
                logging.error(f"Error checking server status: {e}")
                is_running = False
            self.status_updated.emit(is_running)
            self.sleep(5)  # Check every 5 seconds

    def stop(self):
        self._running = False


class TaskProcessor(QThread):
    progress_updated = pyqtSignal(int)
    task_message = pyqtSignal(str)
    task_result = pyqtSignal(str)
    task_status = pyqtSignal(int, int, str)  # task_index, goal_index, status

    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.current_task_index = 0
        self.current_goal_index = 0
        self.context_memory = {}  # Stores results from previous steps

    def run(self):
        total_goals = sum(len(task['Goals']) for task in self.tasks)
        completed_goals = 0

        for i, task in enumerate(self.tasks):
            self.current_task_index = i
            if self._stopped:
                break
            while self._paused:
                self.msleep(100)

            self.task_message.emit(f"Assistant: Starting task '{task['Title']}'")
            self.task_status.emit(i, -1, 'processing')  # Mark task as processing

            for j, goal in enumerate(task['Goals']):
                self.current_goal_index = j
                if self._stopped:
                    break
                while self._paused:
                    self.msleep(100)

                self.task_message.emit(f"Assistant: Processing goal: {goal}")
                self.task_status.emit(i, j, 'processing')  # Mark goal as processing

                result = self.process_goal(task, goal)
                success = self.self_review(goal, result)
                if success:
                    self.task_result.emit(f"Result for goal '{goal}':\n{result}")
                    self.context_memory[goal] = result  # Store result in context
                    self.task_status.emit(i, j, 'completed')  # Mark goal as completed
                else:
                    self.task_message.emit(f"Assistant: Repeating processing for goal: {goal}")
                    self.task_status.emit(i, j, 'error')  # Mark goal as error
                    # Repeat the processing
                    result = self.process_goal(task, goal, retry=True)
                    success = self.self_review(goal, result)
                    if success:
                        self.task_result.emit(f"Result for goal '{goal}' after retry:\n{result}")
                        self.context_memory[goal] = result
                        self.task_status.emit(i, j, 'completed')
                    else:
                        self.task_message.emit(f"Assistant: Could not successfully process goal '{goal}'.")
                        self.task_status.emit(i, j, 'failed')

                completed_goals += 1
                progress = int((completed_goals / total_goals) * 100)
                self.progress_updated.emit(progress)

            self.task_status.emit(i, -1, 'completed')  # Mark task as completed

            # Task summary
            self.task_message.emit(f"Assistant: Creating summary for task: {task['Title']}")
            task_summary = self.create_task_summary(task)
            self.task_result.emit(f"Summary for task '{task['Title']}':\n{task_summary}")
            self.context_memory[task['Title']] = task_summary

        if not self._stopped:
            self.task_message.emit("Assistant: All tasks have been completed.")
            # Generate final result
            final_result = self.generate_final_result()
            self.task_result.emit(f"Final Result:\n{final_result}")

    def process_goal(self, task, goal, retry=False):
        context = self.get_relevant_context(goal)
        retry_text = "This is a retry attempt.\n" if retry else ""

        prompt = f"""
{retry_text}
You are working on a software development task.

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

**Current Goal:**
{goal}

**Relevant Context:**
{context}

Create a solution for the goal above. Use clear and precise language and provide code examples where appropriate.
"""
        result = self.llm_client.generate(self.llm_model, prompt)
        return result

    def self_review(self, goal, result):
        prompt = f"""
Critically review the following result for the goal "{goal}":

{result}

Evaluate if the result is complete, correct, and meets the requirements. Answer with "Yes" if the result is satisfactory, or "No" with a brief explanation if it is not satisfactory.
"""
        review = self.llm_client.generate(self.llm_model, prompt)
        if "Yes" in review:
            return True
        else:
            self.task_message.emit(f"Assistant: Self-review for goal '{goal}' failed. Reason: {review}")
            return False

    def get_relevant_context(self, current_goal):
        # Collect relevant results from context
        context = ""
        for key, value in self.context_memory.items():
            context += f"**{key}:**\n{value}\n\n"
        return context

    def create_task_summary(self, task):
        # Create a summary of the task based on results of the goals
        summaries = []
        for goal in task['Goals']:
            result = self.context_memory.get(goal, '')
            summaries.append(f"{goal}:\n{result}\n")
        task_summary = "\n".join(summaries)
        return task_summary

    def generate_final_result(self):
        # Generate the final result based on all tasks
        context = "\n".join(self.context_memory.values())
        prompt = f"""
Summarize all previous results and generate the complete code of the application including documentation.

**Overall Context:**
{context}

Create a structured and comprehensive summary with all relevant details.
"""
        final_result = self.llm_client.generate(self.llm_model, prompt)
        return final_result

    def pause(self):
        self._paused = True

    def resume(self):
        self._paused = False

    def stop(self):
        self._stopped = True


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Intelligent Assistant for Developers')
        self.setGeometry(100, 100, 1200, 800)
        self.llm_client = LLMClient()
        self.paused = False
        self.stopped = False
        self.plan_data = []
        self.uploaded_files = []
        self.server_status_checker = None
        self.server_process = None  # To manage the server process

        self.init_ui()
        self.apply_styles()

        # Start server status checker
        self.start_server_status_checker()
        # Update LLM list after server status checker starts
        self.update_llm_list()

    def init_ui(self):
        self.create_menu()
        self.create_toolbar()
        self.status_bar = self.statusBar()
        self.status_bar.showMessage('Ready')

        self.tabs = QTabWidget()
        self.tabs.addTab(self.create_configuration_tab(), "Configuration")
        self.tabs.addTab(self.create_plan_tab(), "Planning")
        self.tabs.addTab(self.create_chat_tab(), "Assistant")
        self.setCentralWidget(self.tabs)

    def create_menu(self):
        menu_bar = self.menuBar()

        # File menu
        file_menu = menu_bar.addMenu('File')
        save_action = QAction('Save', self)
        save_action.triggered.connect(self.save_project)
        file_menu.addAction(save_action)
        load_action = QAction('Load', self)
        load_action.triggered.connect(self.load_project)
        file_menu.addAction(load_action)
        exit_action = QAction('Exit', self)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # Help menu
        help_menu = menu_bar.addMenu('Help')
        about_action = QAction('About', self)
        about_action.triggered.connect(self.show_about_dialog)
        help_menu.addAction(about_action)

    def create_toolbar(self):
        tool_bar = self.addToolBar('Tools')
        start_action = QAction('Start', self)
        start_action.triggered.connect(self.start_processing)
        tool_bar.addAction(start_action)
        pause_action = QAction('Pause', self)
        pause_action.triggered.connect(self.pause_processing)
        tool_bar.addAction(pause_action)
        resume_action = QAction('Resume', self)
        resume_action.triggered.connect(self.resume_processing)
        tool_bar.addAction(resume_action)
        stop_action = QAction('Stop', self)
        stop_action.triggered.connect(self.stop_processing)
        tool_bar.addAction(stop_action)

    def create_configuration_tab(self):
        config_widget = QWidget()
        v_layout = QVBoxLayout()

        llm_label = QLabel('Select an LLM:')
        self.llm_combo = QComboBox()

        self.server_status_label = QLabel('Server Status: Unknown')
        self.server_status_indicator = QLabel('⚪')

        self.start_server_btn = QPushButton('Start Server')
        self.stop_server_btn = QPushButton('Stop Server')

        h_layout = QHBoxLayout()
        h_layout.addWidget(llm_label)
        h_layout.addWidget(self.llm_combo)

        h_layout2 = QHBoxLayout()
        h_layout2.addWidget(self.server_status_label)
        h_layout2.addWidget(self.server_status_indicator)
        h_layout2.addWidget(self.start_server_btn)
        h_layout2.addWidget(self.stop_server_btn)

        # Application description
        description_label = QLabel('Application Description:')
        self.description_text = QTextEdit()

        # File upload
        self.upload_btn = QPushButton('Upload Files')
        self.file_list = QListWidget()

        # Settings
        settings_group = QGroupBox('Settings')
        settings_layout = QFormLayout()
        self.token_limit_spin = QSpinBox()
        self.token_limit_spin.setRange(100, 10000)
        self.token_limit_spin.setValue(1000)
        settings_layout.addRow('Token Limit:', self.token_limit_spin)
        settings_group.setLayout(settings_layout)

        v_layout.addLayout(h_layout)
        v_layout.addLayout(h_layout2)
        v_layout.addWidget(description_label)
        v_layout.addWidget(self.description_text)
        v_layout.addWidget(self.upload_btn)
        v_layout.addWidget(QLabel('Uploaded Files:'))
        v_layout.addWidget(self.file_list)
        v_layout.addWidget(settings_group)

        config_widget.setLayout(v_layout)

        # Connections
        self.start_server_btn.clicked.connect(self.start_server)
        self.stop_server_btn.clicked.connect(self.stop_server)
        self.upload_btn.clicked.connect(self.upload_files)

        return config_widget

    def create_plan_tab(self):
        plan_widget = QWidget()
        v_layout = QVBoxLayout()

        # Analysis button
        self.analyse_btn = QPushButton('Start Analysis')
        self.edit_plan_btn = QPushButton('Edit Plan')

        # Plan display
        self.plan_tree = QTreeWidget()
        self.plan_tree.setHeaderLabels(['Task', 'Description/Goals'])
        self.plan_tree.setColumnWidth(0, 300)

        h_button_layout = QHBoxLayout()
        h_button_layout.addWidget(self.analyse_btn)
        h_button_layout.addWidget(self.edit_plan_btn)

        v_layout.addLayout(h_button_layout)
        v_layout.addWidget(self.plan_tree)

        plan_widget.setLayout(v_layout)

        # Connections
        self.analyse_btn.clicked.connect(self.start_analysis)
        self.edit_plan_btn.clicked.connect(self.edit_plan)

        return plan_widget

    def create_chat_tab(self):
        chat_widget = QWidget()
        chat_layout = QVBoxLayout()

        # Chat area
        self.chat_area = QTextBrowser()

        # History area
        self.history_area = QTextEdit()
        self.history_area.setReadOnly(True)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setAlignment(Qt.AlignCenter)

        chat_layout.addWidget(QLabel('Chat Area:'))
        chat_layout.addWidget(self.chat_area)
        chat_layout.addWidget(QLabel('History:'))
        chat_layout.addWidget(self.history_area)
        chat_layout.addWidget(self.progress_bar)

        chat_widget.setLayout(chat_layout)

        return chat_widget

    def apply_styles(self):
        # Optional: Loading a dark mode stylesheet
        try:
            with open("style.qss", "r") as f:
                self.setStyleSheet(f.read())
        except Exception as e:
            logging.error(f"Error loading stylesheet: {e}")

    def update_llm_list(self):
        models = self.llm_client.get_models()
        logging.debug(f"Found models: {models}")
        self.llm_combo.clear()
        if models:
            self.llm_combo.addItems(models)
        else:
            logging.warning("No models found.")

    def start_server_status_checker(self):
        self.server_status_checker = ServerStatusChecker(base_url=self.llm_client.base_url)
        self.server_status_checker.status_updated.connect(self.update_server_status)
        self.server_status_checker.start()

    def update_server_status(self, is_running):
        logging.debug(f"Server status updated: {'Running' if is_running else 'Stopped'}")
        if is_running:
            self.server_status_label.setText('Server Status: Running')
            self.server_status_indicator.setText('🟢')
            self.update_llm_list()
        else:
            self.server_status_label.setText('Server Status: Stopped')
            self.server_status_indicator.setText('🔴')

    def start_server(self):
        if self.server_process is not None and self.server_process.poll() is None:
            QMessageBox.information(self, "Info", "Server is already running.")
            return
        try:
            # Start the server using subprocess
            # 'ollama' should be in PATH or provide full path
            logging.debug("Attempting to start the Ollama server...")
            if os.name == 'nt':  # Windows
                self.server_process = subprocess.Popen(
                    ['ollama', 'serve'],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=True
                )
            else:
                self.server_process = subprocess.Popen(
                    ['ollama', 'serve'],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE
                )
            self.chat_area.append("<b>Assistant:</b> Starting the server...")
            time.sleep(5)
            self.update_server_status(True)
        except Exception as e:
            logging.error(f"Error starting the server: {e}")
            QMessageBox.critical(self, "Error", f"Error starting the server: {e}")

    def stop_server(self):
        if self.server_process is not None and self.server_process.poll() is None:
            try:
                self.server_process.terminate()
                self.server_process.wait()
                self.server_process = None
                self.chat_area.append("<b>Assistant:</b> Server stopped successfully.")
                self.update_server_status(False)
            except Exception as e:
                logging.error(f"Error stopping the server: {e}")
                QMessageBox.critical(self, "Error", f"Error stopping the server: {e}")
        else:
            QMessageBox.information(self, "Info", "Server is not running.")

    def upload_files(self):
        options = QFileDialog.Options()
        files, _ = QFileDialog.getOpenFileNames(
            self, "Select Files", "",
            "All Files (*)", options=options
        )
        if files:
            self.uploaded_files.extend(files)
            self.display_uploaded_files()

    def display_uploaded_files(self):
        self.file_list.clear()
        for file in self.uploaded_files:
            self.file_list.addItem(file)

    def start_analysis(self):
        self.chat_area.append("<b>Assistant:</b> Starting analysis of the application description...")
        thread = threading.Thread(target=self.perform_analysis)
        thread.start()

    def perform_analysis(self):
        description = self.description_text.toPlainText()
        selected_llm = self.llm_combo.currentText()
        if not selected_llm:
            self.chat_area.append("<b>Assistant:</b> No LLM selected. Please select an LLM.")
            return
        file_contents = self.get_file_contents()
        prompt = self.generate_plan_prompt(description, file_contents)
        plan_text = self.llm_client.generate(selected_llm, prompt)
        self.plan_data = self.parse_plan(plan_text)
        if self.plan_data:
            self.display_plan(self.plan_data)
            self.chat_area.append("<b>Assistant:</b> Analysis completed. The plan has been created.")
        else:
            self.chat_area.append("<b>Assistant:</b> Error during analysis.")
            logging.error("Plan data is empty after analysis.")

    def get_file_contents(self):
        content = ""
        for file_path in self.uploaded_files:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content += f"\nContent of {file_path}:\n{f.read()}"
            except Exception as e:
                logging.error(f"Error reading file {file_path}: {e}")
        return content

    def generate_plan_prompt(self, description, file_contents):
        return f"""
As an experienced software architect, please create a detailed development plan based on the following application description.

**Application Description:**
{description}

**Additional Files:**
{file_contents}

**Plan Requirements:**
- Structured JSON output.
- Includes tasks with Title, Description, and Goals.
- Use clear and precise language.

**Example Output:**
[
    {{
        "Title": "Task 1: XYZ",
        "Description": "Description of the task.",
        "Goals": ["Goal 1", "Goal 2"]
    }},
    ...
]
"""

    def parse_plan(self, plan_text):
        try:
            plan = json.loads(plan_text)
            return plan
        except json.JSONDecodeError as e:
            logging.error(f"Error parsing the plan: {e}")
            return []

    def display_plan(self, plan):
        self.plan_tree.clear()
        for i, task in enumerate(plan):
            task_item = QTreeWidgetItem([task['Title']])
            task_item.setData(0, Qt.UserRole, (i, None))  # Store index for identification
            task_item.setBackground(0, QColor('#444'))  # Darker background for dark mode

            description_item = QTreeWidgetItem(['Description', task['Description']])
            task_item.addChild(description_item)

            goals_item = QTreeWidgetItem(['Goals'])
            for j, goal in enumerate(task['Goals']):
                goal_item = QTreeWidgetItem([goal])
                goal_item.setData(0, Qt.UserRole, (i, j))
                goal_item.setBackground(0, QColor('#555'))
                goals_item.addChild(goal_item)
            task_item.addChild(goals_item)

            self.plan_tree.addTopLevelItem(task_item)

    def edit_plan(self):
        selected_item = self.plan_tree.currentItem()
        if selected_item:
            if selected_item.parent():
                # Editing goals or descriptions
                text, ok = QInputDialog.getText(self, 'Edit', 'Enter new text:', QLineEdit.Normal, selected_item.text(0))
                if ok and text:
                    selected_item.setText(0, text)
            else:
                # Editing tasks
                text, ok = QInputDialog.getText(self, 'Edit', 'Enter new task title:', QLineEdit.Normal, selected_item.text(0))
                if ok and text:
                    selected_item.setText(0, text)
        else:
            QMessageBox.warning(self, 'Warning', 'Please select an item to edit.')

    def update_task_status(self, task_index, goal_index, status):
        if goal_index == -1:
            # Update task
            task_item = self.plan_tree.topLevelItem(task_index)
            color = self.get_status_color(status)
            task_item.setBackground(0, color)
        else:
            # Update goal
            task_item = self.plan_tree.topLevelItem(task_index)
            goals_item = task_item.child(1)  # Assuming Goals are the second child
            goal_item = goals_item.child(goal_index)
            color = self.get_status_color(status)
            goal_item.setBackground(0, color)

    def get_status_color(self, status):
        if status == 'processing':
            return QColor('#FFD700')  # Gold
        elif status == 'completed':
            return QColor('#32CD32')  # Lime Green
        elif status == 'error':
            return QColor('#FF4500')  # Orange Red
        elif status == 'failed':
            return QColor('#DC143C')  # Crimson
        else:
            return QColor('#555')  # Default dark

    def start_processing(self):
        if not self.plan_data:
            QMessageBox.warning(self, "Warning", "Please create a plan first.")
            return
        if not self.llm_combo.currentText():
            QMessageBox.warning(self, "Warning", "Please select an LLM.")
            return
        self.chat_area.append("<b>Assistant:</b> Starting the processing of tasks.")
        self.task_processor = TaskProcessor(
            tasks=self.plan_data,
            llm_client=self.llm_client,
            llm_model=self.llm_combo.currentText()
        )
        self.task_processor.progress_updated.connect(self.update_progress)
        self.task_processor.task_message.connect(self.append_chat_message)
        self.task_processor.task_result.connect(self.append_chat_message)
        self.task_processor.task_status.connect(self.update_task_status)
        self.task_processor.start()

    def append_chat_message(self, message):
        self.chat_area.append(f"<b>{message}</b>")
        self.history_area.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.status_bar.showMessage(f'Progress: {value}%')

    def pause_processing(self):
        if hasattr(self, 'task_processor'):
            self.task_processor.pause()
            self.chat_area.append("<b>System:</b> The process has been paused.")

    def resume_processing(self):
        if hasattr(self, 'task_processor'):
            self.task_processor.resume()
            self.chat_area.append("<b>System:</b> The process will continue.")

    def stop_processing(self):
        if hasattr(self, 'task_processor'):
            self.task_processor.stop()
            self.chat_area.append("<b>System:</b> The process has been stopped.")
            self.progress_bar.setValue(0)

    def save_project(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(self, "Save Project", "", "JSON Files (*.json)", options=options)
        if file_name:
            project_data = {
                'description': self.description_text.toPlainText(),
                'uploaded_files': self.uploaded_files,
                'plan_data': self.plan_data
            }
            try:
                with open(file_name, 'w', encoding='utf-8') as f:
                    json.dump(project_data, f, ensure_ascii=False, indent=4)
                QMessageBox.information(self, "Success", "Project successfully saved.")
            except Exception as e:
                logging.error(f"Error saving project: {e}")
                QMessageBox.critical(self, "Error", f"Error saving project: {e}")

    def load_project(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(self, "Load Project", "", "JSON Files (*.json)", options=options)
        if file_name:
            try:
                with open(file_name, 'r', encoding='utf-8') as f:
                    project_data = json.load(f)
                self.description_text.setPlainText(project_data.get('description', ''))
                self.uploaded_files = project_data.get('uploaded_files', [])
                self.display_uploaded_files()
                self.plan_data = project_data.get('plan_data', [])
                if self.plan_data:
                    self.display_plan(self.plan_data)
                QMessageBox.information(self, "Success", "Project successfully loaded.")
            except Exception as e:
                logging.error(f"Error loading project: {e}")
                QMessageBox.critical(self, "Error", f"Error loading project: {e}")

    def show_about_dialog(self):
        QMessageBox.about(self, "About", "Intelligent Assistant for Developers\nVersion 2.2")

    def closeEvent(self, event):
        # Stop the server status checker thread when the application is closed
        if self.server_status_checker is not None:
            self.server_status_checker.stop()
            self.server_status_checker.wait()
        # Stop the server process if it was started
        if self.server_process is not None and self.server_process.poll() is None:
            try:
                self.server_process.terminate()
                self.server_process.wait()
                self.server_process = None
                logging.info("Server process terminated on application exit.")
            except Exception as e:
                logging.error(f"Error stopping the server on exit: {e}")
        event.accept()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())