Redesign this script for a smooth flowing process of having the llm as part of the conversation between people in a room (maybe add this as one of the radio buttons?) please retain the options and only improve the logic where seen fit, redesign the gui and have the llm respond using voice and also react to 'her' name "Misha" : import os
import time
import random
import speech_recognition as sr
import pyttsx3
import numpy as np
import pyaudio
from PyQt6 import QtWidgets, QtGui, QtCore
from gtts import gTTS
from huggingface_hub import InferenceClient
from tempfile import NamedTemporaryFile
import wave
import threading

# Initialize the LLM client
client = InferenceClient(
    model="mistralai/Mistral-7B-Instruct-v0.3",
    token="<TRUFFLEHOG_REDACTED_HUGGINGFACE>"
)

# Initialize text-to-speech
engine = pyttsx3.init()
engine.setProperty('rate', 180)

# Conversation memory
conversation_memory = []
interaction_mode = 0  # Default interaction mode
recognized_text = None  # Variable to store recognized text for processing
lock = threading.Lock()  # Lock for thread safety

class AudioVisualizer(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Mistral Audio Player")
        self.setGeometry(100, 100, 400, 200)
        self.setStyleSheet("background-color: #f0f0f0;")
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update_visualization)

    def start_visualization(self):
        self.timer.start(100)

    def stop_visualization(self):
        self.timer.stop()
        self.update()  # Refresh the widget

    def update_visualization(self):
        self.update()  # Trigger a repaint

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setBrush(QtGui.QColor(0, 100, 200))
        width = self.width() // 10
        for i in range(10):
            height = random.randint(20, 150)
            painter.drawRect(i * width, self.height() - height, width - 2, height)

def play_audio(text):
    """Convert text to speech and play the audio without external player."""
    tts = gTTS(text=text, lang='af')
    
    with NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
        temp_audio_path = temp_audio.name
        tts.save(temp_audio_path)

    os.system(f"start {temp_audio_path}")
    time.sleep(2)  # Wait for the audio to finish playing
    os.remove(temp_audio_path)

def interact_with_llm(prompt):
    """Interact with the LLM and get its response."""
    output_text = ""
    try:
        response = client.chat_completion(
            messages=[{"role": "user", "content": prompt}],
            max_tokens=150,
        )
        output_text = response['choices'][0]['message']['content']
    except Exception as e:
        print(f"Error interacting with LLM: {e}")
    return output_text

def process_recognized_text():
    """Process recognized text in a separate thread."""
    global recognized_text

    while True:
        time.sleep(0.5)  # Small delay to reduce CPU usage
        with lock:
            if recognized_text:
                spoken_text = recognized_text
                recognized_text = None  # Clear recognized text after processing
                print(f"Processing: {spoken_text}")

                if interaction_mode == 1:  # Be silent and listen
                    continue
                elif interaction_mode in [2, 3]:  # Actively respond or sometimes respond
                    llm_feedback = interact_with_llm(spoken_text)
                    if llm_feedback:
                        play_audio(llm_feedback)
                    else:
                        print("LLM did not return a valid response.")

def listen():
    """Listen to the conversation continuously."""
    global recognized_text
    recognizer = sr.Recognizer()
    microphone = sr.Microphone()

    while True:
        print("Listening for conversation...")
        with microphone as source:
            recognizer.adjust_for_ambient_noise(source)
            audio = recognizer.listen(source)

        try:
            spoken_text = recognizer.recognize_google(audio, language="af-ZA")
            print(f"Recognized: {spoken_text}")

            with lock:
                recognized_text = spoken_text  # Store recognized text for processing

        except sr.UnknownValueError:
            print("Sorry, I didn't understand the audio.")
        except sr.RequestError as e:
            print(f"Could not request results; {e}")

def record_voice_profile(profile_name):
    """Record a user's voice and save it as a profile."""
    recognizer = sr.Recognizer()
    microphone = sr.Microphone()

    print("Please read the following sentences:")
    sentences = [
        "Hello, my name is [Your Name].",
        "I am learning to speak Afrikaans.",
        "This is a voice profile test.",
        "How are you today?"
    ]

    for sentence in sentences:
        print(f"Please say: '{sentence}'")
        with microphone as source:
            recognizer.adjust_for_ambient_noise(source)
            audio = recognizer.listen(source)

        try:
            # Save the audio to a file
            audio_file = f"{profile_name}.wav"
            with wave.open(audio_file, 'wb') as wf:
                wf.setnchannels(1)  # mono
                wf.setsampwidth(2)  # sample width
                wf.setframerate(44100)  # frame rate
                wf.writeframes(audio.get_raw_data())
            print(f"Profile '{profile_name}' saved.")
        except Exception as e:
            print(f"Error recording voice: {e}")

class MistralGUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Mistral Interaction")
        self.setGeometry(100, 100, 400, 300)

        self.label = QtWidgets.QLabel("Select Mistral's Interaction Mode:", self)
        self.label.setGeometry(10, 10, 380, 30)

        self.radio_var = QtWidgets.QButtonGroup(self)
        self.radio1 = QtWidgets.QRadioButton("Be silent and listen", self)
        self.radio2 = QtWidgets.QRadioButton("Actively respond", self)
        self.radio3 = QtWidgets.QRadioButton("Sometimes respond", self)
        self.radio4 = QtWidgets.QRadioButton("Record Voice Profile", self)

        self.radio_var.addButton(self.radio1, 0)
        self.radio_var.addButton(self.radio2, 1)
        self.radio_var.addButton(self.radio3, 2)
        self.radio_var.addButton(self.radio4, 3)

        self.radio1.setGeometry(10, 50, 380, 30)
        self.radio2.setGeometry(10, 80, 380, 30)
        self.radio3.setGeometry(10, 110, 380, 30)
        self.radio4.setGeometry(10, 140, 380, 30)

        self.start_button = QtWidgets.QPushButton("Start Listening", self)
        self.start_button.setGeometry(10, 170, 180, 30)
        self.start_button.clicked.connect(self.start_listening)

        self.audio_visualizer = AudioVisualizer()

    def start_listening(self):
        global interaction_mode
        selected_button = self.radio_var.checkedButton()
        interaction_mode = self.radio_var.id(selected_button)  # Update the global interaction mode

        if selected_button == self.radio4:  # Record Voice Profile
            profile_name, ok = QtWidgets.QInputDialog.getText(self, "Input", "Enter profile name:")
            if ok and profile_name:
                record_voice_profile(profile_name)
        else:
            self.audio_visualizer.start_visualization()
            self.audio_visualizer.show()
            threading.Thread(target=process_recognized_text, daemon=True).start()  # Start processing thread
            listen()  # Start listening in the main thread

if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    gui = MistralGUI()
    gui.show()
    threading.Thread(target=listen, daemon=True).start()  # Start listening in a separate thread
    app.exec()