Okay, here is my current script:

import sounddevice as sd
import socket
import json
import threading
import sys
import time
import random
import logging
import ctypes
import os
import re
from fuzzywuzzy import fuzz, process  # For fuzzy matching
import keyboard  # For global key listening
import wave  # For saving audio files (optional)
import whisper
import numpy as np
import traceback
import warnings  # For suppressing warnings
import queue  # For thread-safe queues
from collections import deque

# Flask imports
from flask import Flask, render_template_string, request, jsonify
from flask import Response
import flask
from threading import Thread

# Only import msvcrt if on Windows
if os.name == 'nt':
    import msvcrt
else:
    import select  # For Unix-based systems

######################## Configuration #########################
# Configuration for sending data to the second PC
SECOND_PC_IP = "192.168.1.8"  # Replace with the actual IP address of the second PC
PORT = 50007  # Must match the port on the second PC

# Path to the Whisper model
WHISPER_MODEL_SIZE = "small"  # Options: tiny, base, small, medium, large

# Twitch account aliases (including common variations)
TWITCH_ACCOUNTS = {
    'hugeb0ss': ['huge', 'boss', 'baws'],
    'killerbigproblem8874': ['killer'],
    'd0k6cn1y3d': ['dok', 'doc'],
    # Add more accounts and their aliases if needed
}

# Activation key (Backtick)
ACTIVATION_KEY = ''  # Set to the backtick key

# Log file path
LOG_FILE = "speech_sender.log"

# Optional: Path to save recorded audio for debugging
SAVE_AUDIO = False
AUDIO_SAVE_PATH = "recorded_audio.wav"

# Similarity threshold for fuzzy matching
FUZZY_THRESHOLD = 80  # Adjust based on testing

# Target microphone names for recording
TARGET_MIC_NAMES = [
    'Microphone (USBAudio1.0)',  # Replace with your microphone names
    # Add any other microphones you prefer to use for recording
]

# Timeout settings
INITIAL_CONFIRMATION_TIMEOUT = 8  # Seconds for first confirmation (was 6)
FINAL_CONFIRMATION_TIMEOUT = 20  # Seconds for second confirmation (was 12)
SEND_DELAY_MIN = 5  # Minimum seconds to wait before sending without input (was 7)
SEND_DELAY_MAX = 25  # Maximum seconds to wait before sending without input (was 33)

######################## Logging Setup #########################
# Configure Logging
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.DEBUG,  # Set to DEBUG for troubleshooting
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# Suppress Whisper warnings
warnings.filterwarnings("ignore", category=UserWarning)

######################## Admin Check #########################
def is_running_as_admin():
    """Check if the script is running with administrative privileges."""
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def restart_as_admin():
    """Restart the script with administrative privileges."""
    try:
        # Get the absolute path of the script
        script = os.path.abspath(__file__)

        # Get the executable (python) path
        executable = sys.executable

        # Prepare the command
        params = ' '.join([f'"{arg}"' for arg in sys.argv[1:]])

        # Restart the script with admin rights
        ctypes.windll.shell32.ShellExecuteW(
            None, "runas", executable, f'"{script}" {params}', None, 1)
        logging.info("Restarted script with administrative privileges.")
    except Exception as e:
        logging.error(f"Failed to restart as admin: {e}")
        print(f"Failed to restart as admin: {e}", flush=True)
    sys.exit()

######################## Global Variables ######################
is_recording = False
recorded_audio = []
selected_device_name = ''
sock = None
whisper_model = None
device_index = None  # To keep track of the selected device

# Queue for processing messages sequentially
processing_queue = queue.Queue()

# Queue for confirmation requests
confirmation_queue = queue.Queue()

# Queue for sending confirmations back
response_queue = queue.Queue()

# Log deque for web console (thread-safe with max length)
log_deque = deque(maxlen=1000)
log_lock = threading.Lock()

# Event to signal microphone switch
microphone_switch_event = threading.Event()

# Flask app initialization
app = Flask(__name__)

# HTML Template for the web interface
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
    <title>Speech Sender</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #logs { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; background-color: #f9f9f9; }
        button { padding: 10px 20px; margin: 5px; }
        select { padding: 5px; margin: 5px; }
    </style>
    <script>
        function startRecording() {
            fetch('/start_recording', {method: 'POST'})
            .then(response => response.json())
            .then(data => console.log(data.message));
        }

        function stopRecording() {
            fetch('/stop_recording', {method: 'POST'})
            .then(response => response.json())
            .then(data => console.log(data.message));
        }

        function switchMicrophone() {
            const deviceSelect = document.getElementById('microphoneSelect');
            const selectedDevice = deviceSelect.value;
            fetch('/switch_microphone', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ device_name: selectedDevice })
            })
            .then(response => response.json())
            .then(data => alert(data.message));
        }

        function fetchLogs() {
            fetch('/logs')
            .then(response => response.json())
            .then(data => {
                const logsDiv = document.getElementById('logs');
                logsDiv.innerHTML = data.logs.join('<br>');
                logsDiv.scrollTop = logsDiv.scrollHeight;
            });
        }

        setInterval(fetchLogs, 1000); // Fetch logs every second
    </script>
</head>
<body>
    <h1>Speech Sender Control Panel</h1>
    <div>
        <button onclick="startRecording()">Start Recording</button>
        <button onclick="stopRecording()">Stop Recording</button>
    </div>
    <div>
        <h3>Select Microphone</h3>
        <select id="microphoneSelect">
            {% for device in devices %}
                <option value="{{ device }}">{{ device }}</option>
            {% endfor %}
        </select>
        <button onclick="switchMicrophone()">Switch Microphone</button>
    </div>
    <div>
        <h3>Console Logs</h3>
        <div id="logs"></div>
    </div>
</body>
</html>
"""

######################## Function Definitions ###################

def log_message(message, level='DEBUG'):
    """Add a log message to both the logger and the web console deque."""
    # Log to the logging system
    if level.upper() == 'DEBUG':
        logging.debug(message)
    elif level.upper() == 'INFO':
        logging.info(message)
    elif level.upper() == 'WARNING':
        logging.warning(message)
    elif level.upper() == 'ERROR':
        logging.error(message)
    elif level.upper() == 'CRITICAL':
        logging.critical(message)
    else:
        logging.info(message)

    # Add to the web console log deque
    with log_lock:
        log_deque.append(f"[{level.upper()}] {message}")

def audio_callback(indata, frames, time_info, status):
    """Callback function to collect audio data."""
    if status:
        log_message(f"Stream status: {status}", 'DEBUG')
        print(f"[DEBUG] Stream status: {status}", flush=True)
    global is_recording, recorded_audio
    if is_recording:
        # Collect audio data
        recorded_audio.append(bytes(indata))
        log_message(f"Recorded audio chunk of {len(indata)} bytes.", 'DEBUG')
        print(f"[DEBUG] Recorded audio chunk of {len(indata)} bytes.", flush=True)

def introduce_errors(text):
    """Introduce errors into the text: misspellings a certain percentage of the time, and minor grammatical errors otherwise."""
    rand = random.random()
    if rand < 0.3:  # 30% chance
        # Introduce a misspelling
        words = text.split()
        if len(words) == 0:
            return text  # No words to process

        # Randomly select one word to alter
        word_to_alter_index = random.randint(0, len(words) - 1)
        word_to_alter = words[word_to_alter_index]
        original_word = word_to_alter  # For logging

        # Decide the type of misspelling to introduce
        if len(word_to_alter) > 1 and random.random() < 0.5:
            # Swap two adjacent letters
            if len(word_to_alter) >= 2:
                idx = random.randint(0, len(word_to_alter) - 2)
                word_to_alter = (
                    word_to_alter[:idx] +
                    word_to_alter[idx+1] +
                    word_to_alter[idx] +
                    word_to_alter[idx+2:]
                )
        else:
            # Misspell the word by changing a letter
            if len(word_to_alter) >= 1:
                idx = random.randint(0, len(word_to_alter) - 1)
                word_to_alter = (
                    word_to_alter[:idx] +
                    random.choice('abcdefghijklmnopqrstuvwxyz') +
                    word_to_alter[idx+1:]
                )
        # Replace the word in the list
        words[word_to_alter_index] = word_to_alter
        new_text = ' '.join(words)
        log_message(f"Introduced misspelling in word '{original_word}' -> '{word_to_alter}'", 'DEBUG')
    else:
        # Introduce a grammatical error (remove punctuation)
        new_text = re.sub(r'[^\w\s]', '', text)
        log_message("Introduced grammatical error by removing punctuation.", 'DEBUG')
    return new_text.strip()

def extract_account_and_message(recognized_text):
    """Extract target account and message from recognized text."""
    target_account = None
    message = ""

    # Normalize the recognized text
    recognized_text_lower = recognized_text.lower()

    # Check for aliases
    for account, aliases in TWITCH_ACCOUNTS.items():
        for alias in aliases:
            pattern = r'\b' + re.escape(alias.lower()) + r'\b'
            match = re.search(pattern, recognized_text_lower)
            if match:
                target_account = account
                message = recognized_text[match.end():].strip()
                # Optionally, remove leading connectors
                connectors = ['for', 'to', 'from', 'message', 'send', 'notify',
                              'hi', 'this is', 'okay', 'so']
                connector_pattern = r'^(' + '|'.join(connectors) + r')\s+'
                message = re.sub(connector_pattern, '', message, flags=re.IGNORECASE).strip()
                log_message(f"Extracted account '{account}' and message '{message}' from recognized text.", 'DEBUG')
                return target_account, message

    # If no alias found, cancel the message
    log_message("No known alias found. Canceling message.", 'DEBUG')
    print("[INFO] Unrecognized account. Message canceled.", flush=True)
    log_message("Unrecognized account in message. Message canceled.", 'INFO')
    return None, None

def handle_user_confirmation(account, message):
    """Place a confirmation request into the confirmation queue."""
    confirmation_queue.put((account, message))

def confirmation_handler():
    """Thread function to handle user confirmations."""
    while True:
        try:
            account, message = confirmation_queue.get()
            if not account or not message:
                log_message("Invalid confirmation request, skipping.", 'DEBUG')
                confirmation_queue.task_done()
                continue

            # Step 1: Initial Confirmation (8 seconds)
            log_message(f"\n[CONFIRMATION 1] Transcribed Message for '{account}': '{message}'", 'DEBUG')
            print(f"\n[CONFIRMATION 1] Transcribed Message for '{account}': '{message}'", flush=True)
            logging.info(f"Transcribed Message for '{account}': '{message}'")

            response1 = get_input_timeout("Confirm this message? (Y/N): ", INITIAL_CONFIRMATION_TIMEOUT)

            if response1 is None:
                # No input, proceed to Step 2
                log_message("No input received for Step 1. Proceeding to Step 2.", 'DEBUG')
                print("[DEBUG] No input received for Step 1. Proceeding to Step 2.", flush=True)
            else:
                response1 = response1.strip().lower()
                log_message(f"Step 1 Response: '{response1}'", 'DEBUG')

                if response1 == 'n':
                    log_message("Message canceled by user at Step 1.", 'DEBUG')
                    print("[DEBUG] Message canceled by user at Step 1.", flush=True)
                    confirmation_queue.task_done()
                    continue
                elif response1 != 'y':
                    log_message("Invalid input at Step 1. Proceeding to Step 2.", 'DEBUG')
                    print("[DEBUG] Invalid input at Step 1. Proceeding to Step 2.", flush=True)

            # Step 2: Final Confirmation (20 seconds)
            # Display the account it's going to send to
            log_message(f"\n[CONFIRMATION 2] Account: '{account}'", 'DEBUG')
            log_message(f"Message: '{message}'", 'DEBUG')
            print(f"\n[CONFIRMATION 2] Account: '{account}'", flush=True)
            print(f"Message: '{message}'", flush=True)
            logging.info(f"Account: '{account}', Message: '{message}'")

            response2 = get_input_timeout("Is this correct? (Y/N) or enter corrected message: ", FINAL_CONFIRMATION_TIMEOUT)

            if response2 is None:
                # No input received, assume message is good and send after random delay
                log_message("No input received for Step 2. Assuming message is correct.", 'INFO')
                print("[DEBUG] No input received for Step 2. Assuming message is correct.", flush=True)
                delay = random.randint(SEND_DELAY_MIN, SEND_DELAY_MAX)
                log_message(f"Waiting for {delay} seconds before sending message to account '{account}'.", 'DEBUG')
                print(f"[DEBUG] Waiting for {delay} seconds before sending message to account '{account}'.", flush=True)
                time.sleep(delay)
                send_to_second_pc(account, message)
                log_message(f"Message sent to account '{account}': '{message}'", 'DEBUG')
                print(f"[DEBUG] Message sent to account '{account}': '{message}'", flush=True)
            else:
                response2 = response2.strip().lower()
                log_message(f"Step 2 Response: '{response2}'", 'DEBUG')

                if response2 == 'y':
                    # Send immediately
                    log_message("User confirmed to send the message immediately.", 'INFO')
                    print("[DEBUG] Sending message immediately as per Step 2 confirmation.", flush=True)
                    send_to_second_pc(account, message)
                    log_message(f"Message sent to account '{account}': '{message}'", 'DEBUG')
                    print(f"[DEBUG] Message sent to account '{account}': '{message}'", flush=True)
                elif response2 == 'n':
                    # Cancel the message
                    log_message("User canceled the message at Step 2.", 'INFO')
                    print("[DEBUG] Message canceled by user at Step 2.", flush=True)
                else:
                    # Assume the user entered a corrected message
                    corrected_message = response2
                    if corrected_message:
                        log_message(f"User provided a corrected message: '{corrected_message}'", 'INFO')
                        print(f"[DEBUG] Sending corrected message: '{corrected_message}'", flush=True)
                        send_to_second_pc(account, corrected_message)
                        log_message(f"Corrected message sent to account '{account}': '{corrected_message}'", 'DEBUG')
                        print(f"[DEBUG] Corrected message sent to account '{account}': '{corrected_message}'", flush=True)
                    else:
                        # Empty input, assume message is correct and send after delay
                        log_message("Empty input provided at Step 2. Assuming message is correct.", 'WARNING')
                        print("[DEBUG] Empty input provided at Step 2. Assuming message is correct.", flush=True)
                        delay = random.randint(SEND_DELAY_MIN, SEND_DELAY_MAX)
                        log_message(f"Waiting for {delay} seconds before sending message to account '{account}'.", 'DEBUG')
                        print(f"[DEBUG] Waiting for {delay} seconds before sending message to account '{account}'.", flush=True)
                        time.sleep(delay)
                        send_to_second_pc(account, message)
                        log_message(f"Message sent to account '{account}': '{message}'", 'DEBUG')
                        print(f"[DEBUG] Message sent to account '{account}': '{message}'", flush=True)

            confirmation_queue.task_done()
        except Exception as e:
            log_message(f"Exception in confirmation_handler: {e}", 'ERROR')
            print(f"[ERROR] Exception in confirmation_handler: {e}", flush=True)
            traceback.print_exc()
            confirmation_queue.task_done()
            continue  # Ensure the loop continues

def process_queue():
    """Worker thread to process messages sequentially."""
    while True:
        try:
            # Get the next audio data from the queue
            audio_bytes = processing_queue.get()
            if not isinstance(audio_bytes, bytes):
                log_message("Received non-bytes item in processing queue, skipping.", 'DEBUG')
                processing_queue.task_done()
                continue

            # Transcribe with Whisper
            recognized_text = transcribe_with_whisper(audio_bytes)

            if not recognized_text:
                log_message("No complete text recognized.", 'DEBUG')
                print("[DEBUG] No complete text recognized.", flush=True)
                processing_queue.task_done()
                continue  # Move to next message

            # Extract account and message
            target_account, message = extract_account_and_message(recognized_text)

            if not target_account or not message:
                log_message(f"Could not extract account or message from recognized text: '{recognized_text}'", 'DEBUG')
                print(f"[DEBUG] Could not extract account or message from recognized text: '{recognized_text}'", flush=True)
                processing_queue.task_done()
                continue  # Move to next message

            # Introduce errors into the message (after alias detection)
            message_with_errors = introduce_errors(message)
            log_message(f"Message with errors: '{message_with_errors}'", 'DEBUG')

            # Handle confirmation and sending
            handle_user_confirmation(target_account, message_with_errors)

            processing_queue.task_done()
        except Exception as e:
            log_message(f"Exception in process_queue: {e}", 'ERROR')
            print(f"[ERROR] Exception in process_queue: {e}", flush=True)
            traceback.print_exc()
            processing_queue.task_done()
            continue  # Ensure the loop continues

def get_input_timeout(prompt, timeout):
    """
    Get user input with a timeout. Returns None if no input is received within the timeout.
    This function uses msvcrt for Windows and select for Unix-based systems.
    """
    print(prompt, end='', flush=True)
    start_time = time.time()
    input_str = ''

    if os.name == 'nt':
        while True:
            if msvcrt.kbhit():
                char = msvcrt.getche()
                if char in [b'\r', b'\n']:
                    print('')  # Move to the next line
                    break
                elif char == b'\x08':  # Backspace
                    input_str = input_str[:-1]
                    # Move the cursor back, overwrite with space, move back again
                    print(' \b', end='', flush=True)
                else:
                    try:
                        decoded_char = char.decode()
                        input_str += decoded_char
                    except UnicodeDecodeError:
                        pass  # Ignore non-decodable characters
            if time.time() - start_time > timeout:
                print('')  # Move to the next line
                return None
            time.sleep(0.05)
    else:
        # For Unix-based systems
        import sys
        import select
        while True:
            ready, _, _ = select.select([sys.stdin], [], [], max(0, start_time + timeout - time.time()))
            if ready:
                line = sys.stdin.readline()
                if line:
                    input_str = line.strip()
                    break
            else:
                return None

    return input_str if input_str else None

def transcribe_with_whisper(audio_bytes):
    """Transcribe audio using Whisper."""
    try:
        # Convert audio_bytes to a NumPy array
        audio_np = np.frombuffer(audio_bytes, np.int16).astype(np.float32) / 32768.0  # Normalize
        log_message(f"Converted audio bytes to NumPy array with shape {audio_np.shape}.", 'DEBUG')

        # Transcribe
        log_message("Starting Whisper transcription.", 'DEBUG')
        result = whisper_model.transcribe(audio_np)
        recognized_text = result.get('text', '').strip()
        log_message(f"Whisper recognized text: '{recognized_text}'", 'DEBUG')

        return recognized_text
    except Exception as e:
        log_message(f"Exception in transcribe_with_whisper: {e}", 'ERROR')
        print(f"[ERROR] Exception in transcribe_with_whisper: {e}", flush=True)
        traceback.print_exc()
        return ''

def send_to_second_pc(account, text):
    """Send recognized text to the second PC via TCP, specifying the Twitch account."""
    try:
        if text and account:
            # Format the message as JSON for reliable parsing
            message_dict = {
                "account": account,
                "message": text
            }
            message_json = json.dumps(message_dict) + '\n'  # Adding newline as delimiter
            sock.sendall(message_json.encode('utf-8'))
            log_message(f"Sent message to {SECOND_PC_IP}:{PORT} - Account: {account}, Message: {text}", 'DEBUG')
            print(f"[DEBUG] Sent message to {SECOND_PC_IP}:{PORT} - Account: {account}, Message: {text}", flush=True)
        else:
            log_message("No text or account specified to send.", 'DEBUG')
            print("[DEBUG] No text or account specified to send.", flush=True)
    except socket.error as se:
        log_message(f"Socket error: {se}", 'ERROR')
        print(f"[ERROR] Socket error: {se}", flush=True)
    except Exception as e:
        log_message(f"Failed to send message: {e}", 'ERROR')
        print(f"[ERROR] Failed to send message: {e}", flush=True)

def list_input_devices():
    """List all available input devices."""
    try:
        input_devices = sd.query_devices()
        log_message("Listing available input devices:", 'DEBUG')
        for idx, device in enumerate(input_devices):
            if device['max_input_channels'] > 0:
                log_message(f"{idx}: {device['name']}", 'DEBUG')
                print(f"{idx}: {device['name']}", flush=True)
        return input_devices
    except Exception as e:
        log_message(f"Failed to list input devices: {e}", 'ERROR')
        print(f"[ERROR] Failed to list input devices: {e}", flush=True)
        sys.exit(1)

def select_microphone(input_devices, selected_device=None):
    """Select the microphone matching TARGET_MIC_NAMES or cancel if not found.
    
    If selected_device is provided, selects that device.
    """
    global device_index, selected_device_name

    if selected_device:
        # Attempt to find the device by name
        for idx, device in enumerate(input_devices):
            if device['name'] == selected_device:
                if device['max_input_channels'] > 0:
                    device_index = idx
                    selected_device_name = device['name']
                    log_message(f"Selected microphone: {selected_device_name}", 'INFO')
                    print(f"[DEBUG] Selected microphone: {selected_device_name}", flush=True)
                    return True
        log_message(f"Microphone '{selected_device}' not found.", 'WARNING')
        print(f"[INFO] Microphone '{selected_device}' not found.", flush=True)
        return False
    else:
        # Select based on TARGET_MIC_NAMES
        for idx, device in enumerate(input_devices):
            if device['max_input_channels'] > 0:
                device_name_lower = device['name'].lower()
                for target_name in TARGET_MIC_NAMES:
                    if target_name.lower() in device_name_lower:
                        device_index = idx
                        selected_device_name = device['name']
                        log_message(f"Selected microphone: {selected_device_name}", 'INFO')
                        print(f"[DEBUG] Selected microphone: {selected_device_name}", flush=True)
                        return True

        # If none matched, prompt user to select
        print("[INFO] No matching microphone found. Please ensure the correct microphone is connected.", flush=True)
        log_message("No matching microphone found. Exiting.", 'INFO')
        sys.exit(1)

def save_audio_file(audio_data):
    """Save recorded audio data to a WAV file for debugging."""
    try:
        with wave.open(AUDIO_SAVE_PATH, 'wb') as wf:
            wf.setnchannels(1)  # Mono
            wf.setsampwidth(2)  # 16 bits per sample
            wf.setframerate(16000)  # Sampling rate
            wf.writeframes(audio_data)
        log_message(f"Audio saved to {AUDIO_SAVE_PATH}", 'DEBUG')
        print(f"[DEBUG] Audio saved to {AUDIO_SAVE_PATH}", flush=True)
    except Exception as e:
        log_message(f"Failed to save audio file: {e}", 'ERROR')
        print(f"[ERROR] Failed to save audio file: {e}", flush=True)

def connect_to_second_pc():
    """Establish connection to the second PC with retries."""
    global sock
    while True:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((SECOND_PC_IP, PORT))
            log_message(f"Connected to {SECOND_PC_IP}:{PORT}", 'DEBUG')
            print(f"[DEBUG] Connected to {SECOND_PC_IP}:{PORT}", flush=True)
            break
        except Exception as e:
            log_message(f"Failed to connect to {SECOND_PC_IP}:{PORT} - {e}", 'ERROR')
            print(f"[ERROR] Failed to connect to {SECOND_PC_IP}:{PORT} - {e}", flush=True)
            log_message("Retrying in 5 seconds...", 'DEBUG')
            print("[DEBUG] Retrying in 5 seconds...", flush=True)
            time.sleep(5)

def activation_key_event():
    """Listen for activation key events using the keyboard library."""
    keyboard.on_press_key(ACTIVATION_KEY, lambda _: start_recording())
    keyboard.on_release_key(ACTIVATION_KEY, lambda _: stop_recording())

def start_recording():
    """Start recording audio."""
    global is_recording, recorded_audio
    if not is_recording:
        is_recording = True
        recorded_audio = []  # Reset the buffer
        log_message("Activation key pressed. Start recording.", 'DEBUG')
        print("[DEBUG] Activation key pressed. Start recording.", flush=True)

def stop_recording():
    """Stop recording audio and add it to the processing queue."""
    global is_recording, recorded_audio
    if is_recording:
        is_recording = False
        log_message("Activation key released. Stop recording.", 'DEBUG')
        print("[DEBUG] Activation key released. Stop recording.", flush=True)

        # Introduce a short delay before processing
        time.sleep(0.5)

        # Optionally, save the audio for debugging
        if SAVE_AUDIO:
            save_audio_file(bytes().join(recorded_audio))

        # Add raw audio data to processing queue
        audio_data = bytes().join(recorded_audio)
        processing_queue.put(audio_data)
        log_message("Audio data added to processing queue.", 'DEBUG')
        print("[DEBUG] Audio data added to processing queue.", flush=True)

def handle_exception(exc_type, exc_value, exc_traceback):
    """Log any uncaught exceptions."""
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    logging.critical("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
    traceback.print_exception(exc_type, exc_value, exc_traceback)
    print(f"[CRITICAL] Uncaught exception: {exc_value}", flush=True)
    log_message(f"Uncaught exception: {exc_value}", 'CRITICAL')

sys.excepthook = handle_exception

######################## Flask Routes ###########################

@app.route('/')
def index():
    """Render the main page with controls and logs."""
    input_devices = sd.query_devices()
    device_names = [device['name'] for device in input_devices if device['max_input_channels'] > 0]
    return render_template_string(HTML_TEMPLATE, devices=device_names)

@app.route('/start_recording', methods=['POST'])
def web_start_recording():
    """Start recording via the web interface."""
    start_recording()
    return jsonify({"message": "Recording started."})

@app.route('/stop_recording', methods=['POST'])
def web_stop_recording():
    """Stop recording via the web interface."""
    stop_recording()
    return jsonify({"message": "Recording stopped."})

@app.route('/switch_microphone', methods=['POST'])
def web_switch_microphone():
    """Switch microphone via the web interface."""
    data = request.get_json()
    device_name = data.get('device_name', '').strip()
    if not device_name:
        return jsonify({"message": "No device name provided."}), 400

    input_devices = sd.query_devices()
    success = select_microphone(input_devices, selected_device=device_name)
    if success:
        # If a stream is already open, we need to stop it and start a new one
        microphone_switch_event.set()
        return jsonify({"message": f"Switched to microphone '{device_name}'."})
    else:
        return jsonify({"message": f"Microphone '{device_name}' not found."}), 404

@app.route('/logs')
def get_logs():
    """Provide the latest logs for the web console."""
    with log_lock:
        logs = list(log_deque)
    return jsonify({"logs": logs})

######################## Threading Setup #######################

def run_flask_app():
    """Run the Flask app."""
    app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)

######################## Main Function ##########################

def main():
    global sock, whisper_model, device_index, selected_device_name

    # Check if the script is already elevated
    if not is_running_as_admin():
        log_message("Script not running as admin. Attempting to restart as admin.", 'INFO')
        print("This script requires administrative privileges to function correctly.", flush=True)
        print("Attempting to restart with administrative rights...", flush=True)
        restart_as_admin()

    # Initialize the TCP socket with retry mechanism
    try:
        connect_to_second_pc()
    except Exception as e:
        log_message(f"Unable to establish connection after retries: {e}", 'ERROR')
        print(f"[ERROR] Unable to establish connection after retries: {e}", flush=True)
        sys.exit(1)

    # Load Whisper model
    try:
        whisper_model = whisper.load_model(WHISPER_MODEL_SIZE)
        log_message("Whisper model loaded successfully.", 'INFO')
        print("[DEBUG] Whisper model loaded successfully.", flush=True)
    except Exception as e:
        log_message(f"Failed to load Whisper model: {e}", 'ERROR')
        print(f"[ERROR] Failed to load Whisper model: {e}", flush=True)
        sys.exit(1)

    # List input devices and allow the user to select the desired microphone
    input_devices = list_input_devices()
    print("\nPlease select a microphone by entering the corresponding number:", flush=True)
    for idx, device in enumerate(input_devices):
        if device['max_input_channels'] > 0:
            print(f"{idx}: {device['name']}", flush=True)
    while True:
        try:
            user_input = int(input("Microphone number: ").strip())
            if 0 <= user_input < len(input_devices) and input_devices[user_input]['max_input_channels'] > 0:
                device_index = user_input
                selected_device_name = input_devices[user_input]['name']
                log_message(f"User selected microphone: {selected_device_name}", 'INFO')
                print(f"[DEBUG] Selected microphone: {selected_device_name}", flush=True)
                break
            else:
                print("[ERROR] Invalid selection. Please enter a valid microphone number.", flush=True)
        except ValueError:
            print("[ERROR] Please enter a valid number.", flush=True)

    # Start the confirmation handler thread
    confirmation_thread = threading.Thread(target=confirmation_handler, daemon=True)
    confirmation_thread.start()
    log_message("Confirmation handler thread started.", 'DEBUG')
    print("[DEBUG] Confirmation handler thread started.", flush=True)

    # Start the processing thread
    processing_thread = threading.Thread(target=process_queue, daemon=True)
    processing_thread.start()
    log_message("Processing thread started.", 'DEBUG')
    print("[DEBUG] Processing thread started.", flush=True)

    # Start the Flask web server in a separate thread
    flask_thread = threading.Thread(target=run_flask_app, daemon=True)
    flask_thread.start()
    log_message("Flask web server started on http://0.0.0.0:5000", 'INFO')
    print("[DEBUG] Flask web server started on http://0.0.0.0:5000", flush=True)

    # Start the global activation key listener
    try:
        activation_key_event()
        log_message("Global keyboard listener started.", 'DEBUG')
        print("[DEBUG] Global keyboard listener started.", flush=True)
    except Exception as e:
        log_message(f"Failed to start global keyboard listener: {e}", 'ERROR')
        print(f"[ERROR] Failed to start global keyboard listener: {e}", flush=True)
        sys.exit(1)

    # Function to handle microphone switch events
    def handle_microphone_switch():
        while True:
            microphone_switch_event.wait()
            microphone_switch_event.clear()
            log_message("Handling microphone switch event.", 'INFO')
            # Stop current audio stream and restart with the new device
            # This requires careful handling to ensure the stream is restarted
            # Since the audio stream is managed in the main thread, we signal it to restart
            # For simplicity, we can prompt the user to restart the script to apply changes
            log_message("Microphone switch requested. Restart the audio stream to apply changes.", 'WARNING')
            print("[WARNING] Microphone switch requested. Please restart the script to apply changes.", flush=True)
            # Alternatively, implement dynamic stream reinitialization here
            # Left as a future improvement
            # Currently, the stream cannot be dynamically switched without stopping and restarting it

    # Start the microphone switch handler thread
    microphone_switch_thread = threading.Thread(target=handle_microphone_switch, daemon=True)
    microphone_switch_thread.start()

    # Start the audio stream with the selected microphone
    while True:
        try:
            with sd.RawInputStream(
                samplerate=16000,
                blocksize=2000,  # Reduced blocksize to prevent input overflow
                dtype='int16',
                channels=1,
                device=device_index,
                callback=audio_callback
            ):
                log_message("Audio stream started.", 'DEBUG')
                print("[DEBUG] Hold down the '{}' key to start speaking...".format(ACTIVATION_KEY), flush=True)
                print("[DEBUG] Listening... Press Ctrl+C to stop.", flush=True)
                while True:
                    # Keep the main thread alive and handle confirmations
                    # The confirmation_handler runs in its own thread
                    time.sleep(1)
        except KeyboardInterrupt:
            print("\n[DEBUG] Stopped by user.", flush=True)
            log_message("Script stopped by user.", 'INFO')
            break
        except Exception as e:
            log_message(f"Exception in audio stream: {e}", 'ERROR')
            print(f"[ERROR] Exception in audio stream: {e}", flush=True)
            traceback.print_exc()
            time.sleep(5)  # Wait before retrying

    # Clean up resources
    try:
        sock.close()
        log_message("Socket closed.", 'DEBUG')
        print("[DEBUG] Socket closed.", flush=True)
    except Exception as e:
        log_message(f"Failed to close socket: {e}", 'ERROR')
        print(f"[ERROR] Failed to close socket: {e}", flush=True)
    print("[DEBUG] Script terminated.", flush=True)
    log_message("Script terminated.", 'INFO')

######################## Entry Point ############################

if __name__ == "__main__":
    main()

Right now, I can't type and send messages from the web app, though I can see the console. Also, pressing the start recording button doesnt simulate the pressing of the  until I click stop recording. additionally, [DEBUG] Connected to 192.168.1.8:50007
[INFO] Whisper model loaded successfully.
[DEBUG] Listing available input devices:
[DEBUG] 0: Microsoft Sound Mapper - Input
[DEBUG] 1: Microphone (2- HyperX SoloCast)
[DEBUG] 2: Voicemeeter Out B2 (VB-Audio Vo
[DEBUG] 3: CABLE-B Output (VB-Audio Cable
[DEBUG] 4: Microphone (Razer Barracuda X)
[DEBUG] 5: Voicemeeter Out A3 (VB-Audio Vo
[DEBUG] 6: CABLE-A Output (VB-Audio Cable
[DEBUG] 7: Voicemeeter Out A4 (VB-Audio Vo
[DEBUG] 8: Voicemeeter Out A5 (VB-Audio Vo
[DEBUG] 9: CABLE-D Output (VB-Audio Cable
[DEBUG] 10: Microphone (USBAudio1.0)
[DEBUG] 11: Voicemeeter Out A1 (VB-Audio Vo
[DEBUG] 12: Virtual Mic (Virtual Mic for Au
[DEBUG] 13: Voicemeeter Out B1 (VB-Audio Vo
[DEBUG] 14: Voicemeeter Out B3 (VB-Audio Vo
[DEBUG] 15: Microphone (3- VR P10 Dongle)
[DEBUG] 16: Voicemeeter Out A2 (VB-Audio Vo
[DEBUG] 17: CABLE-C Output (VB-Audio Cable
[DEBUG] 37: Primary Sound Capture Driver
[DEBUG] 38: Microphone (2- HyperX SoloCast)
[DEBUG] 39: Voicemeeter Out B2 (VB-Audio Voicemeeter VAIO)
[DEBUG] 40: CABLE-B Output (VB-Audio Cable B)
[DEBUG] 41: Microphone (Razer Barracuda X)
[DEBUG] 42: Voicemeeter Out A3 (VB-Audio Voicemeeter VAIO)
[DEBUG] 43: CABLE-A Output (VB-Audio Cable A)
[DEBUG] 44: Voicemeeter Out A4 (VB-Audio Voicemeeter VAIO)
[DEBUG] 45: Voicemeeter Out A5 (VB-Audio Voicemeeter VAIO)
[DEBUG] 46: CABLE-D Output (VB-Audio Cable D)
[DEBUG] 47: Microphone (USBAudio1.0)
[DEBUG] 48: Voicemeeter Out A1 (VB-Audio Voicemeeter VAIO)
[DEBUG] 49: Virtual Mic (Virtual Mic for AudioRelay)
[DEBUG] 50: Voicemeeter Out B1 (VB-Audio Voicemeeter VAIO)
[DEBUG] 51: Voicemeeter Out B3 (VB-Audio Voicemeeter VAIO)
[DEBUG] 52: Microphone (3- VR P10 Dongle)
[DEBUG] 53: Voicemeeter Out A2 (VB-Audio Voicemeeter VAIO)
[DEBUG] 54: CABLE-C Output (VB-Audio Cable C)
[DEBUG] 92: Voicemeeter Out B2 (VB-Audio Voicemeeter VAIO)
[DEBUG] 93: CABLE-B Output (VB-Audio Cable B)
[DEBUG] 94: Microphone (Razer Barracuda X)
[DEBUG] 95: Voicemeeter Out A3 (VB-Audio Voicemeeter VAIO)
[DEBUG] 96: CABLE-A Output (VB-Audio Cable A)
[DEBUG] 97: Voicemeeter Out A4 (VB-Audio Voicemeeter VAIO)
[DEBUG] 98: Microphone (2- HyperX SoloCast)
[DEBUG] 99: Voicemeeter Out A5 (VB-Audio Voicemeeter VAIO)
[DEBUG] 100: CABLE-D Output (VB-Audio Cable D)
[DEBUG] 101: Microphone (USBAudio1.0)
[DEBUG] 102: Voicemeeter Out A1 (VB-Audio Voicemeeter VAIO)
[DEBUG] 103: Virtual Mic (Virtual Mic for AudioRelay)
[DEBUG] 104: Voicemeeter Out B1 (VB-Audio Voicemeeter VAIO)
[DEBUG] 105: Voicemeeter Out B3 (VB-Audio Voicemeeter VAIO)
[DEBUG] 106: Microphone (3- VR P10 Dongle)
[DEBUG] 107: Voicemeeter Out A2 (VB-Audio Voicemeeter VAIO)
[DEBUG] 108: CABLE-C Output (VB-Audio Cable C)
[DEBUG] 109: CABLE-D Output (VB-Audio CABLE-D)
[DEBUG] 112: Input (Voicemeeter Point 3)
[DEBUG] 114: Input (Voicemeeter Point 6)
[DEBUG] 115: Voicemeeter Out 1 (Voicemeeter Point 1)
[DEBUG] 116: Voicemeeter Out 4 (Voicemeeter Point 4)
[DEBUG] 117: Voicemeeter Out 7 (Voicemeeter Point 7)
[DEBUG] 119: Input (Voicemeeter Point 1)
[DEBUG] 121: Input (Voicemeeter Point 4)
[DEBUG] 123: Input (Voicemeeter Point 7)
[DEBUG] 124: Voicemeeter Out 2 (Voicemeeter Point 2)
[DEBUG] 125: Voicemeeter Out 5 (Voicemeeter Point 5)
[DEBUG] 126: Voicemeeter Out 8 (Voicemeeter Point 8)
[DEBUG] 128: Input (Voicemeeter Point 2)
[DEBUG] 130: Input (Voicemeeter Point 5)
[DEBUG] 132: Input (Voicemeeter Point 8)
[DEBUG] 133: Voicemeeter Out 3 (Voicemeeter Point 3)
[DEBUG] 134: Voicemeeter Out 6 (Voicemeeter Point 6)
[DEBUG] 135: CABLE-A Output (VB-Audio CABLE-A)
[DEBUG] 140: Microphone (Logi C270 HD WebCam)
[DEBUG] 141: CABLE-B Output (VB-Audio CABLE-B)
[DEBUG] 143: Microphone (USBAudio1.0)
[DEBUG] 144: Microphone (USB Audio Device)
[DEBUG] 145: Microphone (VR P10 Dongle)
[DEBUG] 147: Line In (Realtek USB Audio)
[DEBUG] 148: Microphone (Realtek USB Audio)
[DEBUG] 149: Stereo Mix (Realtek USB Audio)
[DEBUG] 153: Microphone (HyperX SoloCast)
[DEBUG] 154: CABLE-C Output (VB-Audio CABLE-C)
[DEBUG] 156: Microphone (Logi C270 HD WebCam)
[DEBUG] 157: Microphone (Razer Barracuda X)
[DEBUG] 159: Microphone (VDVAD Wave)
[DEBUG] 161: Virtual Mic (AudioRelay Wave)
[INFO] User selected microphone: Microphone (USBAudio1.0)
[DEBUG] Confirmation handler thread started.
[DEBUG] Processing thread started.
[INFO] Flask web server started on http://0.0.0.0:5000
[DEBUG] Global keyboard listener started.
[DEBUG] Audio stream started.
[DEBUG] Activation key pressed. Start recording.
[DEBUG] Activation key released. Stop recording.
[DEBUG] Audio data added to processing queue.
[DEBUG] Converted audio bytes to NumPy array with shape (0,).
[DEBUG] Starting Whisper transcription.
[ERROR] Exception in transcribe_with_whisper: Expected 2D or 3D (batch mode) tensor with possibly 0 batch size and other non-zero dimensions for input, but got: [1, 1, 0]
[DEBUG] No complete text recognized.
[INFO] Selected microphone: Microphone (USBAudio1.0)
[INFO] Handling microphone switch event.
[WARNING] Microphone switch requested. Restart the audio stream to apply changes.
[DEBUG] Activation key pressed. Start recording.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Recorded audio chunk of 4000 bytes.
[DEBUG] Activation key released. Stop recording.
[DEBUG] Audio data added to processing queue.
[DEBUG] Converted audio bytes to NumPy array with shape (58000,).
[DEBUG] Starting Whisper transcription.
[DEBUG] Whisper recognized text: 'All right, let me go ahead and test this ship.'
[DEBUG] No known alias found. Canceling message.
[INFO] Unrecognized account in message. Message canceled.
[DEBUG] Could not extract account or message from recognized text: 'All right, let me go ahead and test this ship.'

it shouldn't show the audio chunks etc

give me the full complete script with these issues fixed, along with when clicking start recording, it shouldsimlate the ` key being pressed until i click stop, then it should start transcribing