Code: import sys
import os
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QLineEdit, QPushButton, QTextEdit, QLabel, QFrame, 
                             QFileDialog, QProgressDialog, QSpinBox)
from PyQt6.QtGui import QIcon, QFont
from PyQt6.QtCore import Qt, QSize, QThread, pyqtSignal, QTimer
from llama_cpp import Llama
import openrag_test
import re

class ChatRTXApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("ChatRTX")
        self.setGeometry(100, 100, 1200, 800)
        self.setStyleSheet("""
            QMainWindow {
                background-color: #1e1e1e;
                color: #ffffff;
            }
            QComboBox, QLineEdit, QPushButton, QTextEdit, QSpinBox {
                background-color: #2d2d2d;
                color: #ffffff;
                border: 1px solid #3d3d3d;
                padding: 5px;
            }
            QPushButton {
                background-color: #3d3d3d;
            }
            QPushButton:hover {
                background-color: #4d4d4d;
            }
            QLabel {
                color: #ffffff;
            }
        """)

        self.llm_model = None
        self.search_engine = openrag_test.LocalSemanticSearchEngine()
        self.model_config = {}
        self.loading_thread = None
        self.loading_progress = None
        self.context_length = 512
        self.loaded_model_name = None 

        self.create_widgets()

        # Timer for handling the response stream
        self.response_timer = QTimer()
        self.response_timer.timeout.connect(self.process_response)
        self.response_timer.setInterval(100)  # Adjust interval if needed

        self.rag_llm = None  # Store the RAG-enabled LLM instance

    def create_widgets(self):
        central_widget = QWidget()
        main_layout = QHBoxLayout()

        # Left panel (AI model and Dataset)
        left_panel = QWidget()
        left_layout = QVBoxLayout()

        # AI model section
        ai_model_frame = QFrame()
        ai_model_frame.setFrameShape(QFrame.Shape.StyledPanel)
        ai_model_layout = QVBoxLayout()

        ai_model_label = QLabel("AI model")
        ai_model_label.setFont(QFont("Arial", 12))
        ai_model_layout.addWidget(ai_model_label)

        self.model_path = QLineEdit()
        self.model_path.setPlaceholderText("Model Path")
        ai_model_layout.addWidget(self.model_path)

        self.browse_model_button = QPushButton(QIcon("path_to_folder_icon.png"), "")
        self.browse_model_button.setIconSize(QSize(20, 20))
        self.browse_model_button.clicked.connect(self.browse_model)
        ai_model_layout.addWidget(self.browse_model_button)

        self.load_model_button = QPushButton("Load Model")
        self.load_model_button.clicked.connect(self.load_model)
        ai_model_layout.addWidget(self.load_model_button)

        ai_model_frame.setLayout(ai_model_layout)
        left_layout.addWidget(ai_model_frame)

        # Dataset section
        dataset_frame = QFrame()
        dataset_frame.setFrameShape(QFrame.Shape.StyledPanel)
        dataset_layout = QVBoxLayout()

        dataset_label = QLabel("Dataset")
        dataset_label.setFont(QFont("Arial", 12))
        dataset_layout.addWidget(dataset_label)

        self.dataset_path = QLineEdit()
        self.dataset_path.setPlaceholderText("Folder Path")
        dataset_layout.addWidget(self.dataset_path)

        self.browse_dataset_button = QPushButton(QIcon("path_to_folder_icon.png"), "")
        self.browse_dataset_button.setIconSize(QSize(20, 20))
        self.browse_dataset_button.clicked.connect(self.browse_dataset)
        dataset_layout.addWidget(self.browse_dataset_button)

        self.load_dataset_button = QPushButton("Load Dataset")
        self.load_dataset_button.clicked.connect(self.load_dataset)
        dataset_layout.addWidget(self.load_dataset_button)

        # Context Length
        context_length_layout = QHBoxLayout()
        context_length_label = QLabel("Context Length:")
        context_length_label.setFont(QFont("Arial", 10))
        context_length_layout.addWidget(context_length_label)

        self.context_length_spinbox = QSpinBox()
        self.context_length_spinbox.setRange(1, 4096)
        self.context_length_spinbox.setValue(self.context_length)
        self.context_length_spinbox.valueChanged.connect(self.update_context_length)
        context_length_layout.addWidget(self.context_length_spinbox)

        dataset_layout.addLayout(context_length_layout)

        dataset_frame.setLayout(dataset_layout)
        left_layout.addWidget(dataset_frame)

        left_panel.setLayout(left_layout)
        main_layout.addWidget(left_panel)

        # Right panel (Chat interface)
        right_panel = QWidget()
        right_layout = QVBoxLayout()

        self.chat_history = QTextEdit()
        self.chat_history.setReadOnly(True)
        right_layout.addWidget(self.chat_history)

        # Input area
        input_layout = QHBoxLayout()
        self.input_area = QLineEdit()
        self.input_area.setPlaceholderText("ChatRTX: Type your question")
        input_layout.addWidget(self.input_area)

        self.send_button = QPushButton("SEND")
        self.send_button.setStyleSheet("background-color: #76b900; color: #ffffff;")
        self.send_button.clicked.connect(self.send_message)
        input_layout.addWidget(self.send_button)

        right_layout.addLayout(input_layout)

        right_panel.setLayout(right_layout)
        main_layout.addWidget(right_panel)

        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

    def browse_model(self):
        model_path = QFileDialog.getOpenFileName(self, "Select Model File")[0]
        if model_path:
            self.model_path.setText(model_path)

    def load_model(self):
        model_path = self.model_path.text()
        if not model_path:
            self.chat_history.append("Error: Please select a model file.")
            return

        try:
            self.llm_model = Llama(model_path=model_path)
            self.chat_history.append(f"LLM model '{os.path.basename(model_path)}' loaded successfully.")
        except Exception as e:
            self.chat_history.append(f"Error: Failed to load model: {str(e)}")

    def browse_dataset(self):
        folder_path = QFileDialog.getExistingDirectory(self, "Select Dataset Folder")
        if folder_path:
            self.dataset_path.setText(folder_path)

    def load_dataset(self):
        dataset_path = self.dataset_path.text()
        if not dataset_path:
            self.chat_history.append("Error: Please select a dataset folder.")
            return
        
        self.loading_thread = DatasetLoadingThread(dataset_path, self.search_engine)
        self.loading_thread.progress_update.connect(self.update_progress)
        self.loading_thread.finished.connect(self.loading_finished)
        self.loading_thread.start()

        self.loading_progress = QProgressDialog("Loading Dataset...", "Cancel", 0, 100, self)
        self.loading_progress.setWindowModality(Qt.WindowModality.WindowModal)
        self.loading_progress.show()

    def update_progress(self, value):
        self.loading_progress.setValue(value)

    def loading_finished(self):
        self.loading_progress.close()
        if self.loading_thread.error:
            self.chat_history.append(f"Error: {self.loading_thread.error}")
        else:
            self.chat_history.append("Dataset loaded successfully.")

    def send_message(self):
        user_message = self.input_area.text().strip()
        if not user_message:
            return

        if not self.llm_model:
            self.chat_history.append("Error: Please load an LLM model first.")
            return

        if not self.search_engine.corpus:
            self.chat_history.append("Error: Please load a dataset first.")
            return

        try:
            # Create RAG-enabled LLM instance
            self.rag_llm = openrag_test.RAGEnabledLLM(self.llm_model, self.search_engine)

            # Start the timer for processing streamed response
            self.response_timer.start()

            self.chat_history.append(f"You: {user_message}")
            self.chat_history.append("ChatRTX: ")

            # Initialize the response buffer
            self.rag_llm.response_update.connect(self._stream_response_update)
            self._stream_response_update("")  # Trigger process_response with an empty string

            # Send the message to the model (start the stream)
            self.rag_llm.generate_response(user_message, context_length=self.context_length, stream=True)

            self.input_area.clear()
        except Exception as e:
            self.chat_history.append(f"Error: An error occurred during processing: {str(e)}")
            print(f"Detailed error: {e}")

    def _stream_response_update(self, token):
        # Handle "
" tokens to avoid unwanted output
        if token == "
":
            return 

        # If the response buffer is empty, add an empty string to trigger processing
        if not self.rag_llm.response:
            self.rag_llm.response = ""  # Add an empty string to trigger processing

        self.chat_history.insertPlainText(token)
        self.chat_history.ensureCursorVisible()

    def update_context_length(self, value):
        self.context_length = value

    def extract_questions(self, response):
        """Extract questions from the response using a special token."""
        questions = []
        question_token = "?"  # You can change this token if needed
        parts = response.split(question_token)
        for part in parts[1:]:  # Skip the first part
            # Look for the question start indicator:
            if "<=Context:" in part:
                questions.append(part.split("<=Context:")[0].strip())
            elif "```" in part:
                questions.append(part.split("```")[0].strip()) 
        return questions
    
    def process_response(self):
        """Process the streamed response and extract the final answer."""
        print(f"Processing response: {self.rag_llm.response}")  # Add this line
        if self.rag_llm and self.rag_llm.response:
            response = self.rag_llm.response.strip()
        
            # Remove backticks and 'ANSWER:' prefix
            response = re.sub(r'`+', '', response)
            response = re.sub(r'^ANSWER:\s*', '', response, flags=re.IGNORECASE)
        
            # Extract the answer part (first non-empty line)
            answer_lines = [line.strip() for line in response.split('\n') if line.strip()]
            if answer_lines:
                answer = answer_lines[0]
            
                # Clear previous content and display the final answer
                self.chat_history.clear()
                self.chat_history.append("Dataset loaded successfully.")
                if self.loaded_model_name:
                    self.chat_history.append(f"LLM model '{self.loaded_model_name}' loaded successfully.")
                else:
                    self.chat_history.append("LLM model loaded successfully.")
                self.chat_history.append(f"You: {self.input_area.text()}")
                self.chat_history.append(f"ChatRTX: {answer}")
        
            self.response_timer.stop()  # Stop the timer when processing is done
            self.rag_llm.response = ""  # Reset the response buffer

class DatasetLoadingThread(QThread):
    progress_update = pyqtSignal(int)
    finished = pyqtSignal()
    error = pyqtSignal(str)

    def __init__(self, dataset_path, search_engine):
        super().__init__()
        self.dataset_path = dataset_path
        self.search_engine = search_engine
        self.error = None

    def run(self):
        try:
            self.search_engine.load_documents(self.dataset_path)
        except Exception as e:
            self.error = str(e)
        finally:
            self.finished.emit()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ChatRTXApp()
    window.show()
    sys.exit(app.exec())  ; Error: Detailed error: 'str' object has no attribute 'append'