erstelle eine kritik für diese anwendung:


import sys
import os
import re
import subprocess
import json
import threading
import sqlite3
import time
import logging
import requests
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit,
    QPushButton, QLabel, QDialog, QFormLayout, QSlider, QMessageBox, QComboBox,
    QTextBrowser, QScrollArea, QListWidget, QListWidgetItem, QProgressBar, QInputDialog, QTextEdit
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QObject, QRegularExpression
from PyQt5.QtGui import QTextCursor, QColor, QFont, QTextCharFormat, QSyntaxHighlighter, QPainter, QPen

# NOTE: The entire code and application interface have been translated to English for consistency and broader accessibility.

# Logging configuration
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def find_ollama_executable():
    if os.name == 'nt':  # Windows
        direct_path = "C:\\Users\\Admin\\AppData\\Local\\Programs\\Ollama\\ollama.exe"
        if os.path.exists(direct_path):
            return direct_path
            
        alt_path = os.path.join("C:", "Users", "Admin", "AppData", "Local", "Programs", "Ollama", "ollama.exe")
        if os.path.exists(alt_path):
            return alt_path

    return None

# Database Manager
class DatabaseManager:
    def __init__(self, db_name="autollm_context.db", max_context_length=10000):
        try:
            self.conn = sqlite3.connect(db_name, check_same_thread=False)
            self.max_context_length = max_context_length
            self.create_table()
        except sqlite3.Error as e:
            logging.error(f"Database connection error: {e}")
    
    def create_table(self):
        with self.conn:
            self.conn.execute('''
                CREATE TABLE IF NOT EXISTS context (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    sender TEXT NOT NULL,
                    message TEXT NOT NULL,
                    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
                )
            ''')

    def add_message(self, sender, message):
        with self.conn:
            self.conn.execute("INSERT INTO context (sender, message) VALUES (?, ?)", (sender, message))
            self.ensure_context_limit()

    def get_context(self, max_length=None):
        if max_length is None:
            max_length = self.max_context_length
        cursor = self.conn.cursor()
        cursor.execute("SELECT sender, message FROM context ORDER BY id DESC")
        rows = cursor.fetchall()
        context = []
        current_length = 0
        for row in reversed(rows):
            msg = f"{row[0]}: {row[1]}"
            msg_length = len(msg)
            if current_length + msg_length > max_length:
                break
            context.insert(0, msg)
            current_length += msg_length
        return "\n".join(context)

    def clear_context(self):
        with self.conn:
            self.conn.execute("DELETE FROM context")

    def ensure_context_limit(self):
        cursor = self.conn.cursor()
        cursor.execute("SELECT SUM(LENGTH(message)) FROM context")
        total_length = cursor.fetchone()[0] or 0
        if total_length > self.max_context_length:
            cursor.execute("DELETE FROM context WHERE id IN (SELECT id FROM context ORDER BY id ASC LIMIT 100)")
            self.conn.commit()

    def close_connection(self):
        if self.conn:
            self.conn.close()

# TaskWidget for visual representation of tasks
class TaskWidget(QWidget):
    def __init__(self, task_name, goal, status="Pending"):
        super().__init__()
        self.task_name = task_name
        self.goal = goal
        self.status = status
        self.dependencies = []  # List of task names this task depends on
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()
        self.name_label = QLabel(f"Task: {self.task_name}")
        self.goal_label = QLabel(f"Goal: {self.goal}")
        self.status_label = QLabel(f"Status: {self.status}")
        layout.addWidget(self.name_label)
        layout.addWidget(self.goal_label)
        layout.addWidget(self.status_label)
        self.setLayout(layout)

    def set_status(self, status):
        self.status = status
        self.status_label.setText(f"Status: {self.status}")
        self.update()

    def add_dependency(self, task_name):
        if task_name not in self.dependencies:
            self.dependencies.append(task_name)

# Code Highlighter
class CodeHighlighter(QSyntaxHighlighter):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.highlightingRules = []
        format_keyword = QTextCharFormat()
        format_keyword.setForeground(QColor("orange"))
        keywords = [
            "if", "else", "for", "while", "def", "return", "class", "import", "from",
            "try", "except", "elif", "with", "as", "and", "or", "not", "in", "is", "lambda"
        ]
        for keyword in keywords:
            pattern = QRegularExpression(r"\b" + keyword + r"\b")
            self.highlightingRules.append((pattern, format_keyword))

    def highlightBlock(self, text):
        for pattern, format in self.highlightingRules:
            match_iterator = pattern.globalMatch(text)
            while match_iterator.hasNext():
                match = match_iterator.next()
                self.setFormat(match.capturedStart(), match.capturedLength(), format)

# Ollama Controller
class OllamaController(QObject):
    server_status_changed = pyqtSignal(bool)
    response_received = pyqtSignal(dict)
    error_occurred = pyqtSignal(str)
    llm_list_updated = pyqtSignal(list)
    summarization_received = pyqtSignal(dict)

    def __init__(self):
        super().__init__()
        self.process = None
        self.selected_llm = None
        self.api_port = 11434
        self.server_started = False
        self.interrupt_event = threading.Event()
        self.thread_lock = threading.Lock()
        self.check_server_status()

    def check_server_status(self):
        try:
            response = requests.get(f"http://localhost:{self.api_port}/api/tags", timeout=2)
            if response.status_code == 200:
                self.server_started = True
                self.server_status_changed.emit(True)
                self.update_llm_list()
            else:
                self.server_started = False
                self.server_status_changed.emit(False)
                self.error_occurred.emit(f"API check failed: {response.status_code}")
        except requests.exceptions.RequestException:
            self.server_started = False
            self.server_status_changed.emit(False)
            self.error_occurred.emit("Failed to connect to the server.")

    def update_llm_list(self):
        llms = self.get_installed_llms()
        if llms:
            self.llm_list_updated.emit(llms)
            if not self.selected_llm:
                self.selected_llm = llms[0]
        else:
            self.llm_list_updated.emit([])
            self.selected_llm = None

    def start_server(self):
        if not self.is_server_running():
            try:
                ollama_path = find_ollama_executable()
                if not ollama_path:
                    self.error_occurred.emit("Ollama executable not found.")
                    return

                startupinfo = None
                if os.name == 'nt':
                    startupinfo = subprocess.STARTUPINFO()
                    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

                self.process = subprocess.Popen(
                    [ollama_path, 'serve'],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=False,
                    startupinfo=startupinfo,
                    cwd=os.path.expanduser("~")
                )
                threading.Thread(target=self.delayed_check, daemon=True).start()
                
            except Exception as e:
                self.error_occurred.emit(f"Error starting server: {str(e)}")
        else:
            self.server_started = True
            self.server_status_changed.emit(True)
            self.update_llm_list()

    def delayed_check(self):
        time.sleep(5)
        self.check_server_status()

    def stop_server(self):
        if self.is_server_running() and self.process:
            try:
                self.interrupt_event.set()
                self.process.terminate()
                self.process.wait(timeout=10)
                self.server_started = False
                self.server_status_changed.emit(False)
                self.process = None
                self.selected_llm = None
                self.llm_list_updated.emit([])
            except Exception as e:
                self.error_occurred.emit(f"Error stopping server: {e}")

    def get_installed_llms(self):
        try:
            response = requests.get(f"http://localhost:{self.api_port}/api/tags", timeout=2)
            response.raise_for_status()
            data = response.json()
            return [model["name"] for model in data.get("models", [])]
        except requests.exceptions.RequestException as e:
            self.error_occurred.emit(f"Error fetching models: {e}")
            return []

    def is_server_running(self):
        try:
            response = requests.get(f"http://localhost:{self.api_port}/api/tags", timeout=2)
            return response.status_code == 200
        except Exception:
            return False

    def set_selected_llm(self, llm_name):
        self.selected_llm = llm_name

    def send_request(self, endpoint, data):
        if not self.is_server_running():
            self.error_occurred.emit("Ollama server is not running.")
            return
        threading.Thread(target=self._handle_request, args=(endpoint, data), daemon=True).start()

    def _handle_request(self, endpoint, data):
        self.interrupt_event.clear()
        try:
            url = f"http://localhost:{self.api_port}{endpoint}"
            response = requests.post(url, json=data, stream=True)
            response.raise_for_status()

            for line in response.iter_lines():
                if self.interrupt_event.is_set():
                    raise InterruptedError("User interrupted the request.")
                if line:
                    decoded_line = line.decode('utf-8')
                    try:
                        data = json.loads(decoded_line)
                        if "error" in data:
                            raise Exception(f"Ollama API Error: {data['error']}")
                        self.response_received.emit(data)
                    except json.JSONDecodeError as e:
                        self.error_occurred.emit(f"Invalid JSON response: {e}")
                        return
        except InterruptedError:
            self.error_occurred.emit("Request was interrupted.")
        except requests.exceptions.RequestException as e:
            self.error_occurred.emit(f"API request failed: {e}")
        except Exception as e:
            self.error_occurred.emit(f"Error processing request: {e}")

# Task Manager
class TaskManager(QObject):
    task_completed = pyqtSignal(str)
    task_failed = pyqtSignal(str)
    progress_updated = pyqtSignal(int, int)  # (completed, total)
    final_answer_ready = pyqtSignal(str)
    message_sent = pyqtSignal(str, str, str)  # (sender, message, message_type)

    def __init__(self, ollama_controller, context_manager):
        super().__init__()
        self.ollama = ollama_controller
        self.context_manager = context_manager
        self.tasks = {}  # task_name: TaskWidget
        self.task_queue = []
        self.completed_tasks = []
        self.failed_tasks = []
        self.current_task = None
        self.total_tasks = 0
        self.completed_count = 0
        self.finalizing = False
        self.ollama.response_received.connect(self.handle_response)
        self.ollama.error_occurred.connect(self.on_error)

    def add_task(self, user_request):
        try:
            planning_prompt = (
                "Please create a detailed plan to break down the following request into meaningful subtasks. "
                "Each subtask should be a clear, independent step that can be completed in one go. "
                "Return the steps in a numbered list format with a summary at the end. Use '__END__' to mark the end of the response."
                "\n\n"
                f"Request: {user_request}"
            )
            self.current_user_request = user_request
            # Assuming planning uses the Planning LLM
            self.ollama.set_selected_llm(self.get_planning_llm())
            self.ollama.send_request("/api/generate", {
                "model": self.get_planning_llm(),
                "prompt": planning_prompt,
                "options": {
                    "temperature": 0.5,
                    "max_new_tokens": 1500,
                    "stream": True,  # Enable streaming for planning
                    "format": "json"
                }
            })
            self.total_tasks = 0
            self.completed_count = 0
            self.task_queue.clear()
            self.completed_tasks.clear()
            self.failed_tasks.clear()
            self.current_task = None
            self.finalizing = False
            self.progress_updated.emit(self.completed_count, self.total_tasks)
        except requests.exceptions.RequestException as e:
            self.message_sent.emit("System", f"Error creating plan: {e}", "error")
            logging.error(f"Error creating plan: {e}")

    def handle_response(self, response):
        if self.finalizing:
            if "response" in response:
                final_answer = response["response"]
                if "__END__" in final_answer:
                    final_answer = final_answer.replace("__END__", "")
                    self.final_answer_ready.emit(final_answer)
            return

        if self.current_task:
            # Handling subtask response
            if "response" in response:
                result = response["response"].strip()
                self.append_subtask_result(result)
        else:
            # Handling planning response
            if "response" in response:
                chunk = response["response"]
                if "__END__" in chunk:
                    chunk = chunk.replace("__END__", "")
                    steps, summary = self.parse_planning(chunk)
                    if steps:
                        self.task_queue.extend(steps)
                        self.total_tasks += len(steps)
                        self.progress_updated.emit(self.completed_count, self.total_tasks)
                        self.process_next_task()
                    if summary:
                        self.context_manager.add_message("System", f"Summary: {summary}")
                elif "__CONTINUE__" in chunk:
                    # Await more chunks
                    pass
                else:
                    steps, summary = self.parse_planning(chunk)
                    if steps:
                        self.task_queue.extend(steps)
                        self.total_tasks += len(steps)
                        self.progress_updated.emit(self.completed_count, self.total_tasks)
                        self.process_next_task()
                    if summary:
                        self.context_manager.add_message("System", f"Summary: {summary}")
            else:
                self.task_failed.emit("No response from LLM.")

    def parse_planning(self, planning_text):
        """
        Parses the planning response to extract subtasks and summary.
        Expects a numbered list of subtasks followed by a summary.
        """
        steps = re.findall(r'^\d+\.\s+(.*)', planning_text, re.MULTILINE)
        summary_match = re.search(r'Summary:\s*(.*)', planning_text, re.IGNORECASE)
        summary = summary_match.group(1).strip() if summary_match else ""
        return steps, summary

    def process_next_task(self):
        if self.current_task is not None:
            return
        if not self.task_queue:
            if not self.finalizing:
                self.finalize()
            return
        self.current_task = self.task_queue.pop(0)
        subtask = self.current_task
        task_prompt = (
            f"Please execute the following task in one step and provide the result. "
            f"Use '__END__' to mark the end of your response.\n\n{subtask}\n\nResult:"
        )
        # Reset context for subtask processing
        self.context_manager.clear_context()
        self.context_manager.add_message("System", f"Executing subtask: {subtask}")
        # Assuming processing uses the Processing LLM
        self.ollama.set_selected_llm(self.get_processing_llm())
        self.ollama.send_request("/api/generate", {
            "model": self.get_processing_llm(),
            "prompt": task_prompt,
            "options": {
                "temperature": 0.5,
                "max_new_tokens": 1500,
                "stream": False,  # No streaming for subtasks
                "format": "json"
            }
        })
        self.start_typing_animation()

    def append_subtask_result(self, result):
        """
        Append the result of a subtask, verify goals, and save if appropriate.
        """
        # Append result to completed tasks
        self.completed_tasks.append(self.current_task)
        self.message_sent.emit("System", f"Task '{self.current_task}' completed with result: {result}", "info")
        self.completed_count += 1
        self.progress_updated.emit(self.completed_count, self.total_tasks)
        self.current_task = None
        self.process_next_task()

    def finalize(self):
        if self.finalizing:
            return
        self.finalizing = True
        results = "\n".join([f"{i+1}. {task}" for i, task in enumerate(self.completed_tasks)])
        verification_prompt = (
            f"Please verify that the following tasks have achieved all their goals. "
            f"Respond with 'Ok to save' if all goals are met, or provide detailed feedback otherwise."
            f"\n\nTasks:\n{results}"
            f"\n\nVerification Result:"
        )
        # Assuming verification uses the Final Answer LLM
        self.ollama.set_selected_llm(self.get_final_llm())
        self.ollama.send_request("/api/generate", {
            "model": self.get_final_llm(),
            "prompt": verification_prompt,
            "options": {
                "temperature": 0.3,
                "max_new_tokens": 500,
                "stream": False,  # No streaming for verification
                "format": "json"
            }
        })
        self.start_typing_animation()

    def save_summaries(self):
        # Summarize the results
        summary_prompt = (
            f"Please summarize the results of the following tasks without losing critical information:\n\n"
            f"{chr(10).join([f'Task {i+1}: {task}' for i, task in enumerate(self.completed_tasks)])}\n\nSummary:"
        )
        # Assuming summarization uses the Final Answer LLM
        self.ollama.set_selected_llm(self.get_final_llm())
        self.ollama.send_request("/api/generate", {
            "model": self.get_final_llm(),
            "prompt": summary_prompt,
            "options": {
                "temperature": 0.5,
                "max_new_tokens": 1000,
                "stream": False,  # No streaming for summarization
                "format": "json"
            }
        })
        self.start_typing_animation()

    def handle_verification_response(self, response):
        if "response" in response:
            verification_result = response["response"].strip()
            if verification_result.lower() == "ok to save":
                # Proceed to save summarized results
                self.save_summaries()
            else:
                self.task_failed.emit(f"Verification failed: {verification_result}")
        else:
            self.task_failed.emit("No verification response from LLM.")
        self.stop_typing_animation()

    def handle_summarization_response(self, response):
        if "response" in response:
            summary = response["response"].strip()
            if summary:
                self.context_manager.add_message("System", f"Summary: {summary}")
                self.message_sent.emit("System", "Ok to save", "info")
                self.final_answer_ready.emit("All tasks verified and summaries saved.")
        else:
            self.task_failed.emit("No summarization response from LLM.")
        self.stop_typing_animation()

    def on_error(self, error_message):
        if self.finalizing:
            # Handle verification or summarization errors
            if "save" in error_message.lower():
                self.task_failed.emit(f"Error during saving summaries: {error_message}")
        elif self.current_task:
            self.failed_tasks.append(self.current_task)
            self.completed_count += 1
            self.progress_updated.emit(self.completed_count, self.total_tasks)
            self.task_failed.emit(f"Error in task '{self.current_task}': {error_message}")
            self.current_task = None
            self.process_next_task()
        else:
            self.task_failed.emit(f"Error: {error_message}")

    def interrupt_processing(self):
        try:
            self.ollama.interrupt_event.set()
            self.message_sent.emit("System", "LLM processing has been interrupted.", "warning")
            logging.info("LLM processing has been interrupted.")
        except Exception as e:
            self.message_sent.emit("System", f"Error interrupting process: {e}", "error")
            logging.error(f"Error interrupting process: {e}")

    # Helper methods to get selected LLMs
    def get_planning_llm(self):
        return self.planning_llm_combo.currentText()

    def get_processing_llm(self):
        return self.processing_llm_combo.currentText()

    def get_final_llm(self):
        return self.final_llm_combo.currentText()

# Context Manager
class ContextManager(QObject):
    summarization_requested = pyqtSignal(str)

    def __init__(self, ollama_controller):
        super().__init__()
        self.db = DatabaseManager()
        self.summary = ""
        self.ollama = ollama_controller
        self.ollama.summarization_received.connect(self.handle_summarization_response)
        self.summarization_requested.connect(self.summarize_context)
        self.summarize_request_event = threading.Event()
        self.summarization_result = ""
        self.lock = threading.Lock()

    def add_message(self, sender, message):
        self.db.add_message(sender, message)
        self.check_and_summarize_context()

    def get_context(self):
        with self.lock:
            if self.summary:
                return self.summary + "\n" + self.db.get_context()
            return self.db.get_context()

    def clear_context(self):
        self.db.clear_context()
        with self.lock:
            self.summary = ""

    def check_and_summarize_context(self):
        context = self.db.get_context()
        if len(context) > self.db.max_context_length * 0.8:
            self.summarization_requested.emit(context)

    def summarize_context(self, context_text):
        summary_prompt = (
            "Please summarize the following context to make room for new information. "
            "Keep all important details and key points:\n\n"
            f"{context_text}"
        )
        self.summarization_result = ""
        self.summarize_request_event.clear()

        self.ollama.send_request("/api/generate", {
            "model": self.ollama.selected_llm,
            "prompt": summary_prompt,
            "options": {
                "temperature": 0.5,
                "max_new_tokens": 1000,
                "stream": False,
                "format": "json"
            }
        })

        threading.Thread(target=self.wait_for_summary, daemon=True).start()

    def wait_for_summary(self):
        if self.summarize_request_event.wait(timeout=60):
            if self.summarization_result:
                with self.lock:
                    self.summary = self.summarization_result
                self.db.clear_context()
                self.db.add_message("System", f"Summary of previous context:\n{self.summary}")
        else:
            logging.error("Timeout during context summarization.")
            self.summarize_request_event.set()

    def handle_summarization_response(self, response):
        if "response" in response:
            summary = response["response"].strip()
            if summary:
                with self.lock:
                    self.summarization_result = summary
        else:
            logging.error("No response received for summarization.")
        self.summarize_request_event.set()

# Settings Dialog
class SettingsDialog(QDialog):
    settings_updated = pyqtSignal(dict)

    def __init__(self, current_settings, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Settings")
        self.current_settings = current_settings
        self.init_ui()

    def init_ui(self):
        layout = QFormLayout()

        # Temperature Slider
        self.temperature_slider = QSlider(Qt.Horizontal)
        self.temperature_slider.setRange(0, 100)
        self.temperature_slider.setValue(int(self.current_settings.get("temperature", 0.5) * 100))
        self.temperature_slider.valueChanged.connect(self.update_temperature_label)
        self.temperature_label = QLabel(f"{self.current_settings.get('temperature', 0.5):.2f}")
        temp_layout = QHBoxLayout()
        temp_layout.addWidget(self.temperature_slider)
        temp_layout.addWidget(self.temperature_label)
        layout.addRow("Temperature:", temp_layout)

        # Max Tokens Slider
        self.output_slider = QSlider(Qt.Horizontal)
        self.output_slider.setRange(100, 2000)
        self.output_slider.setValue(self.current_settings.get("max_new_tokens", 1500))
        self.output_slider.valueChanged.connect(self.update_output_label)
        self.output_label = QLabel(str(self.current_settings.get("max_new_tokens", 1500)))
        output_layout = QHBoxLayout()
        output_layout.addWidget(self.output_slider)
        output_layout.addWidget(self.output_label)
        layout.addRow("Max Tokens:", output_layout)

        # Additional options
        self.additional_options_box = QLabel("Additional Options (JSON):")
        self.additional_options_input = QLineEdit()
        self.additional_options_input.setPlaceholderText('e.g., "top_p": 0.9, "frequency_penalty": 1.0')
        layout.addRow(self.additional_options_box, self.additional_options_input)

        # Save and Cancel Buttons
        buttons_layout = QHBoxLayout()
        self.save_button = QPushButton("Save")
        self.save_button.clicked.connect(self.save_settings)
        self.cancel_button = QPushButton("Cancel")
        self.cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(self.save_button)
        buttons_layout.addWidget(self.cancel_button)
        layout.addRow(buttons_layout)

        self.setLayout(layout)

    def update_temperature_label(self, value):
        temperature = value / 100
        self.temperature_label.setText(f"{temperature:.2f}")

    def update_output_label(self, value):
        self.output_label.setText(str(value))

    def save_settings(self):
        try:
            additional_text = self.additional_options_input.text().strip()
            additional_options = {}
            if additional_text:
                additional_options = json.loads("{" + additional_text + "}")
            temperature = self.temperature_slider.value() / 100
            output_length = self.output_slider.value()
            settings = {
                "temperature": temperature,
                "max_new_tokens": output_length,
                "additional_options": additional_options
            }
            self.settings_updated.emit(settings)
            self.accept()
        except json.JSONDecodeError:
            QMessageBox.warning(self, "Invalid JSON", "Please enter valid JSON for additional options.")

# Connection Widget to draw lines between tasks based on dependencies
class ConnectionWidget(QWidget):
    def __init__(self, task_widgets):
        super().__init__()
        self.task_widgets = task_widgets  # Dictionary mapping task_name to TaskWidget
        self.setMinimumWidth(400)  # Adjust as needed

    def update_connections(self):
        self.update()  # Trigger paintEvent

    def paintEvent(self, event):
        painter = QPainter(self)
        pen = QPen(QColor("white"), 2, Qt.SolidLine)
        painter.setPen(pen)

        # Example: Traverse through all tasks and draw lines from dependencies
        for task_name, widget in self.task_widgets.items():
            for dep in widget.dependencies:
                if dep in self.task_widgets:
                    start_widget = self.task_widgets[dep]
                    end_widget = widget
                    # Calculate positions relative to the ConnectionWidget
                    parent_widget = self.parent()
                    if parent_widget:
                        # Map widget positions to the coordinate system of the ConnectionWidget
                        start_pos = parent_widget.mapTo(self, start_widget.pos()) + start_widget.rect().bottomRight()
                        end_pos = parent_widget.mapTo(self, end_widget.pos()) + end_widget.rect().topLeft()
                        painter.drawLine(start_pos, end_pos)

        painter.end()

# AutoLLM App
class AutoLLMApp(QWidget):
    loading_signal = pyqtSignal(bool)  # Loading signal for process indication

    def __init__(self):
        super().__init__()
        self.setWindowTitle("AutoLLM")
        self.resize(1600, 1000)  # Larger window size for better overview
        self.ollama = OllamaController()
        self.context_manager = ContextManager(self.ollama)
        self.current_parameters = {"temperature": 0.5, "max_new_tokens": 1500, "additional_options": {}}

        # Initialize UI
        self.init_ui()
        self.ollama.start_server()
        self.enable_ui(False)

        # Task Manager
        self.task_manager = TaskManager(self.ollama, self.context_manager)
        self.task_manager.task_completed.connect(self.on_task_completed_step)
        self.task_manager.task_failed.connect(self.on_task_failed_step)
        self.task_manager.progress_updated.connect(self.update_progress)
        self.task_manager.final_answer_ready.connect(self.display_final_answer)
        self.task_manager.message_sent.connect(self.display_message)

        self.ollama.server_status_changed.connect(self.update_server_status)
        self.ollama.error_occurred.connect(self.show_error)
        self.ollama.llm_list_updated.connect(self.update_llm_list)

        # Initialize Code Highlighter
        self.highlighter = CodeHighlighter(self.chat_area.document())

        # For blinking "..."
        self.blink_timer = QTimer()
        self.blink_timer.timeout.connect(self.toggle_ellipsis)
        self.ellipsis_visible = True

        # Connect message_sent signal
        self.task_manager.message_sent.connect(self.display_message)

    def init_ui(self):
        main_layout = QHBoxLayout()

        left_layout = QVBoxLayout()

        # Chat area with scrollbar using QTextEdit for HTML support
        self.chat_area = QTextEdit()
        self.chat_area.setReadOnly(True)
        self.chat_area.setFont(QFont("Courier", 10))
        self.chat_area.setStyleSheet("""
            QTextEdit {
                background-color: #2b2b2b;
                color: #ffffff;
                border: 1px solid #5c5c5c;
                padding: 5px;
            }
        """)

        chat_scroll_area = QScrollArea()
        chat_scroll_area.setWidgetResizable(True)
        chat_scroll_area.setWidget(self.chat_area)
        left_layout.addWidget(chat_scroll_area)

        # Input field and send button
        input_layout = QHBoxLayout()
        self.input_field = QLineEdit()
        self.input_field.setPlaceholderText("Enter your message here...")
        self.input_field.returnPressed.connect(self.send_message)
        self.send_button = QPushButton("Send")
        self.send_button.clicked.connect(self.send_message)
        input_layout.addWidget(self.input_field)
        input_layout.addWidget(self.send_button)
        left_layout.addLayout(input_layout)

        # Interrupt button
        self.interrupt_button = QPushButton("Interrupt")
        self.interrupt_button.clicked.connect(self.interrupt_processing)
        left_layout.addWidget(self.interrupt_button)

        # Server monitoring area
        server_layout = QHBoxLayout()
        self.server_status = QLabel("🔴 Server Stopped")
        self.server_button = QPushButton("Start Server")
        self.server_button.clicked.connect(self.toggle_server)
        server_layout.addWidget(self.server_status)
        server_layout.addWidget(self.server_button)
        left_layout.addLayout(server_layout)

        # LLM selection for Planning, Processing, and Final Answer
        llm_selection_layout = QFormLayout()

        self.planning_llm_combo = QComboBox()
        self.planning_llm_combo.setEnabled(False)
        self.planning_llm_combo.setToolTip("Select LLM for Planning")
        llm_selection_layout.addRow("Planning LLM:", self.planning_llm_combo)

        self.processing_llm_combo = QComboBox()
        self.processing_llm_combo.setEnabled(False)
        self.processing_llm_combo.setToolTip("Select LLM for Processing Subtasks")
        llm_selection_layout.addRow("Processing LLM:", self.processing_llm_combo)

        self.final_llm_combo = QComboBox()
        self.final_llm_combo.setEnabled(False)
        self.final_llm_combo.setToolTip("Select LLM for Final Answer")
        llm_selection_layout.addRow("Final Answer LLM:", self.final_llm_combo)

        left_layout.addLayout(llm_selection_layout)

        # Settings button
        self.settings_button = QPushButton("Settings")
        self.settings_button.clicked.connect(self.open_settings)
        left_layout.addWidget(self.settings_button)

        # Progress area
        progress_layout = QVBoxLayout()
        self.progress_bar_planning = QProgressBar()
        self.progress_bar_planning.setValue(0)
        self.progress_label_planning = QLabel("Planning Progress:")
        self.progress_bar_processing = QProgressBar()
        self.progress_bar_processing.setValue(0)
        self.progress_label_processing = QLabel("Processing Progress:")
        progress_layout.addWidget(self.progress_label_planning)
        progress_layout.addWidget(self.progress_bar_planning)
        progress_layout.addWidget(self.progress_label_processing)
        progress_layout.addWidget(self.progress_bar_processing)
        left_layout.addLayout(progress_layout)

        # Plan and steps area with TaskWidget
        self.task_area = QVBoxLayout()
        self.task_area.setSpacing(10)
        self.task_area.setContentsMargins(10, 10, 10, 10)

        self.task_widgets = {}  # Mapping from task_name to TaskWidget

        self.task_scroll_area = QScrollArea()
        self.task_scroll_area.setWidgetResizable(True)
        task_container = QWidget()
        task_container.setLayout(self.task_area)
        self.task_scroll_area.setWidget(task_container)
        left_layout.addWidget(self.task_scroll_area)

        main_layout.addLayout(left_layout, 3)

        # Right layout for visual connections between tasks
        right_layout = QVBoxLayout()
        self.connection_widget = ConnectionWidget(self.task_widgets)
        self.connection_widget.setMinimumWidth(400)
        right_layout.addWidget(self.connection_widget)
        main_layout.addLayout(right_layout, 1)

        # Add loading indicator to the main layout
        self.loading_indicator = QLabel("Loading...")
        self.loading_indicator.setVisible(False)  # Initially hidden
        self.loading_indicator.setStyleSheet("color: yellow; font-weight: bold;")
        main_layout.addWidget(self.loading_indicator)

        self.setLayout(main_layout)

    def classify_message(self, message):
        """
        Determine if the message is simple or requires planning.
        Returns 'simple' or 'complex'.
        """
        # Simple heuristic: if message length is short or contains certain keywords
        simple_keywords = ['hello', 'hi', 'whats up', 'how are you', 'bye']
        if len(message) < 20:
            for keyword in simple_keywords:
                if keyword.lower() in message.lower():
                    return 'simple'
        return 'complex'

    def send_message(self):
        user_message = self.input_field.text().strip()
        if user_message:
            self.context_manager.add_message("User", user_message)
            self.append_message("User", user_message)
            self.input_field.clear()
            
            message_type = self.classify_message(user_message)
            if message_type == 'simple':
                self.handle_simple_message(user_message)
            else:
                self.task_manager.add_task(user_message)
                self.start_typing_animation()  # Start loading indicator

    def handle_simple_message(self, message):
        """
        Handle simple user messages with direct LLM response.
        """
        direct_prompt = (
            f"Respond to the user's message in a clear and concise manner.\n\nUser: {message}\n\nResponse:"
        )
        # Assuming simple messages use the Processing LLM
        self.ollama.set_selected_llm(self.get_processing_llm())
        self.ollama.send_request("/api/generate", {
            "model": self.get_processing_llm(),
            "prompt": direct_prompt,
            "options": {
                "temperature": 0.5,
                "max_new_tokens": 500,
                "stream": False,  # No streaming for simple messages
                "format": "json"
            }
        })
        self.start_typing_animation()  # Start loading indicator
        self.ollama.response_received.connect(self.handle_simple_response)

    def handle_simple_response(self, response):
        if "response" in response:
            reply = response["response"].strip()
            self.append_message("LLM", reply)
        else:
            self.append_message("System", "No response from LLM.", message_type="error")
        self.stop_typing_animation()  # Stop loading indicator
        # Disconnect to prevent multiple connections
        self.ollama.response_received.disconnect(self.handle_simple_response)

    def append_message(self, sender, message, message_type="normal"):
        color = "gray"
        if sender == "User":
            color = "#00ccff"  # Change color for better visibility
            prefix = "<b>User:</b> "
        elif sender == "LLM":
            color = "green"
            prefix = "<b>LLM:</b> "
        else:
            prefix = "<b>System:</b> "

        formatted_message = self.format_message(message)
        html_message = f'<p style="color: {color};">{prefix}{formatted_message}</p>'
        self.chat_area.insertHtml(html_message + "<br>")
        self.chat_area.moveCursor(QTextCursor.End)

    def format_message(self, message):
        message = re.sub(r'(https?://\S+)', r'<a href="\1">\1</a>', message)
        message = re.sub(
            r'```(.*?)```',
            r'<pre style="background-color:#f0f0f0; padding:5px; font-family: monospace; white-space: pre-wrap;"><code>\1</code></pre>',
            message,
            flags=re.DOTALL | re.MULTILINE
        )
        return message

    def display_message(self, sender, message, message_type="normal"):
        self.append_message(sender, message, message_type)

    def interrupt_processing(self):
        self.task_manager.interrupt_processing()
        self.display_message("System", "LLM processing has been interrupted.", "warning")
        self.stop_typing_animation()  # Stop loading animation on interrupt

    def toggle_server(self):
        if self.ollama.is_server_running():
            reply = QMessageBox.question(self, 'Stop Server',
                                         'Are you sure you want to stop the Ollama server?',
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.ollama.stop_server()
        else:
            self.ollama.start_server()

    def open_settings(self):
        settings_dialog = SettingsDialog(self.current_parameters, self)
        settings_dialog.settings_updated.connect(self.update_parameters)
        settings_dialog.exec_()

    def update_parameters(self, params):
        self.current_parameters = params
        QMessageBox.information(self, "Settings Updated", "LLM parameters have been updated.")

    def on_task_completed_step(self, result):
        self.append_message("LLM", f"Result: {result}")
        self.task_manager.completed_tasks.append(self.task_manager.current_task)
        self.task_manager.completed_count += 1
        self.update_progress(self.task_manager.completed_count, self.task_manager.total_tasks)
        self.task_manager.current_task = None
        self.task_manager.process_next_task()
        self.stop_typing_animation()  # Stop loading indicator
        self.update_plan_list()

    def on_task_failed_step(self, error):
        self.append_message("System", f"Error: {error}", message_type="error")
        self.task_manager.failed_tasks.append(self.task_manager.current_task)
        self.task_manager.completed_count += 1
        self.update_progress(self.task_manager.completed_count, self.task_manager.total_tasks)
        self.task_manager.current_task = None
        self.task_manager.process_next_task()
        self.stop_typing_animation()  # Stop loading indicator
        self.update_plan_list()

    def update_progress(self, completed, total):
        if total == 0:
            self.progress_bar_planning.setValue(0)
            self.progress_bar_processing.setValue(0)
        else:
            planning_progress = int((completed / total) * 100)
            processing_progress = planning_progress  # Adjust as needed

            self.progress_bar_planning.setValue(planning_progress)
            self.progress_bar_processing.setValue(processing_progress)
        self.connection_widget.update_connections()

    def update_plan_list(self):
        self.clear_task_area()
        # Display completed tasks
        for task in self.task_manager.completed_tasks:
            widget = TaskWidget(task, goal="Completed", status="Completed")
            self.task_area.addWidget(widget)
            self.task_widgets[task] = widget
        # Display current task
        if self.task_manager.current_task:
            widget = TaskWidget(self.task_manager.current_task, goal="In Progress", status="Processing")
            self.task_area.addWidget(widget)
            self.task_widgets[self.task_manager.current_task] = widget
        # Display queued tasks
        for task in self.task_manager.task_queue:
            widget = TaskWidget(task, goal="Planned", status="Pending")
            self.task_area.addWidget(widget)
            self.task_widgets[task] = widget
        # Display failed tasks
        for task in self.task_manager.failed_tasks:
            widget = TaskWidget(task, goal="Failed", status="Error")
            self.task_area.addWidget(widget)
            self.task_widgets[task] = widget

        # Update connections
        self.connection_widget.update_connections()

    def clear_task_area(self):
        while self.task_area.count():
            child = self.task_area.takeAt(0)
            if child.widget():
                child.widget().deleteLater()

    def display_final_answer(self, answer):
        self.append_message("LLM", f"Final Answer: {answer}")
        self.stop_typing_animation()  # Stop loading indicator

    # Helper methods to get selected LLMs
    def get_planning_llm(self):
        return self.planning_llm_combo.currentText()

    def get_processing_llm(self):
        return self.processing_llm_combo.currentText()

    def get_final_llm(self):
        return self.final_llm_combo.currentText()

# Connection Widget to draw lines between tasks based on dependencies
class ConnectionWidget(QWidget):
    def __init__(self, task_widgets):
        super().__init__()
        self.task_widgets = task_widgets  # Dictionary mapping task_name to TaskWidget
        self.setMinimumWidth(400)  # Adjust as needed

    def update_connections(self):
        self.update()  # Trigger paintEvent

    def paintEvent(self, event):
        painter = QPainter(self)
        pen = QPen(QColor("white"), 2, Qt.SolidLine)
        painter.setPen(pen)

        # Example: Traverse through all tasks and draw lines from dependencies
        for task_name, widget in self.task_widgets.items():
            for dep in widget.dependencies:
                if dep in self.task_widgets:
                    start_widget = self.task_widgets[dep]
                    end_widget = widget
                    # Calculate positions relative to the ConnectionWidget
                    parent_widget = self.parent()
                    if parent_widget:
                        # Map widget positions to the coordinate system of the ConnectionWidget
                        start_pos = parent_widget.mapTo(self, start_widget.pos()) + start_widget.rect().bottomRight()
                        end_pos = parent_widget.mapTo(self, end_widget.pos()) + end_widget.rect().topLeft()
                        painter.drawLine(start_pos, end_pos)

        painter.end()

# Main Execution
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyleSheet("""
        QWidget {
            background-color: #2b2b2b;
            color: #ffffff;
        }
        QPushButton {
            background-color: #3c3f41;
            border: none;
            padding: 5px;
            color: #ffffff;
            border-radius: 3px;
        }
        QPushButton:hover {
            background-color: #4b4e52;
        }
        QLineEdit {
            background-color: #3c3f41;
            border: 1px solid #5c5c5c;
            padding: 5px;
            color: #ffffff;
        }
        QTextEdit {
            background-color: #2b2b2b;
            color: #ffffff;
            padding: 5px;
        }
        QTextBrowser {
            background-color: #2b2b2b;
            border: 1px solid #5c5c5c;
            padding: 5px;
            color: #ffffff;
        }
        QComboBox {
            background-color: #3c3f41;
            border: 1px solid #5c5c5c;
            padding: 5px;
            color: #ffffff;
        }
        QLabel {
            color: #ffffff;
        }
        QListWidget {
            background-color: #2b2b2b;
            border: 1px solid #5c5c5c;
            color: #ffffff;
        }
        QProgressBar {
            border: 1px solid #5c5c5c;
            text-align: center;
            color: #ffffff;
        }
        QProgressBar::chunk {
            background-color: #4caf50;
        }
    """)
    window = AutoLLMApp()
    window.show()
    sys.exit(app.exec_())