Functions/GenXdev.AI/New-LLMAudioChat.ps1

###############################################################################
<#
.SYNOPSIS
Creates an interactive audio chat session with an LLM model.
 
.DESCRIPTION
Initiates a voice-based conversation with a language model, supporting audio
input and output. The function handles audio recording, transcription, model
queries, and text-to-speech responses. Supports multiple language models and
various configuration options.
 
.PARAMETER Query
Initial text query to send to the model. Can be empty to start with voice
input.
 
.PARAMETER Instructions
System instructions/prompt to guide the model's behavior.
 
.PARAMETER Attachments
Array of file paths to attach to the conversation for context.
 
.PARAMETER AudioTemperature
Temperature setting for audio input recognition. Range: 0.0-1.0. Default: 0.0
 
.PARAMETER Temperature
Temperature for response randomness. Range: 0.0-1.0. Default: 0.2
 
.PARAMETER TemperatureResponse
Temperature for controlling response randomness. Range: 0.0-1.0. Default: 0.01
 
.PARAMETER Language
Language to detect in audio input. Default: "English"
 
.PARAMETER CpuThreads
Number of CPU threads to use. 0=auto. Default: 0
 
.PARAMETER Cpu
The number of CPU cores to dedicate to AI operations
 
.PARAMETER SuppressRegex
Regex pattern to suppress tokens from output.
 
.PARAMETER AudioContextSize
Size of the audio context window.
 
.PARAMETER SilenceThreshold
Silence detect threshold (0..32767 defaults to 30)
 
.PARAMETER LengthPenalty
Penalty factor for response length. Range: 0-1
 
.PARAMETER EntropyThreshold
Threshold for entropy in responses. Range: 0-1
 
.PARAMETER LogProbThreshold
Threshold for log probability in responses. Range: 0-1
 
.PARAMETER NoSpeechThreshold
Threshold for no-speech detection. Range: 0-1. Default: 0.1
 
.PARAMETER LLMQueryType
The type of LLM query
 
.PARAMETER Model
The model identifier or pattern to use for AI operations
 
.PARAMETER HuggingFaceIdentifier
The LM Studio specific model identifier
 
.PARAMETER MaxToken
The maximum number of tokens to use in AI operations
 
.PARAMETER Gpu
GPU offloading configuration. -2=Auto, -1=LM Studio decides, 0-1=fraction of
layers. Default: -1
 
.PARAMETER ImageDetail
Image detail level setting. Options: "low", "medium", "high". Default: "low"
 
.PARAMETER ApiEndpoint
The API endpoint URL for AI operations
 
.PARAMETER ApiKey
The API key for authenticated AI operations
 
.PARAMETER TimeoutSeconds
The timeout in seconds for AI operations
 
.PARAMETER ResponseFormat
A JSON schema for the requested output format
 
.PARAMETER MarkupBlocksTypeFilter
Will only output markup blocks of the specified types
 
.PARAMETER PreferencesDatabasePath
Database path for preference data files
 
.PARAMETER ExposedCmdLets
Array of PowerShell command definitions available as tools to the model.
 
.PARAMETER IncludeThoughts
Switch to include model's thought process in output.
 
.PARAMETER DontAddThoughtsToHistory
Switch to include model's thoughts in output
 
.PARAMETER ContinueLast
Switch to continue from last conversation context.
 
.PARAMETER DontSpeak
Switch to disable text-to-speech for AI responses.
 
.PARAMETER DontSpeakThoughts
Switch to disable text-to-speech for AI thought responses.
 
.PARAMETER NoVOX
Switch to disable silence detection for automatic recording stop.
 
.PARAMETER UseDesktopAudioCapture
Switch to use desktop audio capture instead of microphone input.
 
.PARAMETER NoContext
Switch to disable context usage in conversation.
 
.PARAMETER WithBeamSearchSamplingStrategy
Switch to enable beam search sampling strategy.
 
.PARAMETER OnlyResponses
Switch to suppress recognized text in output.
 
.PARAMETER NoSessionCaching
Switch to disable session caching.
 
.PARAMETER OutputMarkupBlocksOnly
Will only output markup block responses
 
.PARAMETER ShowWindow
Switch to show the LM Studio window during operation.
 
.PARAMETER Force
Switch to force stop LM Studio before initialization.
 
.PARAMETER SessionOnly
Use alternative settings stored in session for AI preferences
 
.PARAMETER ClearSession
Clear alternative settings stored in session for AI preferences
 
.PARAMETER SkipSession
Store settings only in persistent preferences without affecting session
 
.EXAMPLE
New-LLMAudioChat -Query "Tell me about PowerShell" `
    -Model "qwen2.5-14b-instruct" `
    -Temperature 0.7 `
    -MaxToken 4096
 
.EXAMPLE
llmaudiochat "What's the weather?" -DontSpeak
#>

###############################################################################
function New-LLMAudioChat {

    [CmdletBinding(SupportsShouldProcess = $true)]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
    [Alias("llmaudiochat")]

    param(
        #######################################################################
        [Parameter(
            ValueFromPipeline = $true,
            Mandatory = $false,
            Position = 0,
            HelpMessage = "Initial query text to send to the model"
        )]
        [AllowEmptyString()]
        [string] $Query = "",
        #######################################################################
        [Parameter(
            Mandatory = $false,
            Position = 1,
            HelpMessage = "System instructions for the model"
        )]
        [string] $Instructions,
        #######################################################################
        [Parameter(
            Position = 2,
            Mandatory = $false,
            HelpMessage = "Array of file paths to attach"
        )]
        [string[]] $Attachments = @(),
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Temperature for audio input recognition (0.0-1.0)"
        )]
        [ValidateRange(0.0, 1.0)]
        [double] $AudioTemperature = 0.0,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Temperature for response randomness (0.0-1.0)"
        )]
        [ValidateRange(0.0, 1.0)]
        [double] $Temperature = 0.2,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("The temperature parameter for controlling the " +
                "randomness of the response.")
        )]
        [ValidateRange(0.0, 1.0)]
        [double] $TemperatureResponse = 0.01,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Sets the language to detect"
        )]
        [ValidateSet(
            "Afrikaans",
            "Akan",
            "Albanian",
            "Amharic",
            "Arabic",
            "Armenian",
            "Azerbaijani",
            "Basque",
            "Belarusian",
            "Bemba",
            "Bengali",
            "Bihari",
            "Bork, bork, bork!",
            "Bosnian",
            "Breton",
            "Bulgarian",
            "Cambodian",
            "Catalan",
            "Cherokee",
            "Chichewa",
            "Chinese (Simplified)",
            "Chinese (Traditional)",
            "Corsican",
            "Croatian",
            "Czech",
            "Danish",
            "Dutch",
            "Elmer Fudd",
            "English",
            "Esperanto",
            "Estonian",
            "Ewe",
            "Faroese",
            "Filipino",
            "Finnish",
            "French",
            "Frisian",
            "Ga",
            "Galician",
            "Georgian",
            "German",
            "Greek",
            "Guarani",
            "Gujarati",
            "Hacker",
            "Haitian Creole",
            "Hausa",
            "Hawaiian",
            "Hebrew",
            "Hindi",
            "Hungarian",
            "Icelandic",
            "Igbo",
            "Indonesian",
            "Interlingua",
            "Irish",
            "Italian",
            "Japanese",
            "Javanese",
            "Kannada",
            "Kazakh",
            "Kinyarwanda",
            "Kirundi",
            "Klingon",
            "Kongo",
            "Korean",
            "Krio (Sierra Leone)",
            "Kurdish",
            "Kurdish (Soranî)",
            "Kyrgyz",
            "Laothian",
            "Latin",
            "Latvian",
            "Lingala",
            "Lithuanian",
            "Lozi",
            "Luganda",
            "Luo",
            "Macedonian",
            "Malagasy",
            "Malay",
            "Malayalam",
            "Maltese",
            "Maori",
            "Marathi",
            "Mauritian Creole",
            "Moldavian",
            "Mongolian",
            "Montenegrin",
            "Nepali",
            "Nigerian Pidgin",
            "Northern Sotho",
            "Norwegian",
            "Norwegian (Nynorsk)",
            "Occitan",
            "Oriya",
            "Oromo",
            "Pashto",
            "Persian",
            "Pirate",
            "Polish",
            "Portuguese (Brazil)",
            "Portuguese (Portugal)",
            "Punjabi",
            "Quechua",
            "Romanian",
            "Romansh",
            "Runyakitara",
            "Russian",
            "Scots Gaelic",
            "Serbian",
            "Serbo-Croatian",
            "Sesotho",
            "Setswana",
            "Seychellois Creole",
            "Shona",
            "Sindhi",
            "Sinhalese",
            "Slovak",
            "Slovenian",
            "Somali",
            "Spanish",
            "Spanish (Latin American)",
            "Sundanese",
            "Swahili",
            "Swedish",
            "Tajik",
            "Tamil",
            "Tatar",
            "Telugu",
            "Thai",
            "Tigrinya",
            "Tonga",
            "Tshiluba",
            "Tumbuka",
            "Turkish",
            "Turkmen",
            "Twi",
            "Uighur",
            "Ukrainian",
            "Urdu",
            "Uzbek",
            "Vietnamese",
            "Welsh",
            "Wolof",
            "Xhosa",
            "Yiddish",
            "Yoruba",
            "Zulu"
        )]
        [string] $Language,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Number of CPU threads to use, defaults to 0 " +
                "(auto)")
        )]
        [int] $CpuThreads = 0,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The number of CPU cores to dedicate to AI operations"
        )]
        [int] $Cpu,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Regex to suppress tokens from the output"
        )]
        [string] $SuppressRegex = $null,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Size of the audio context"
        )]
        [int] $AudioContextSize,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Silence detect threshold (0..32767 defaults " +
                "to 30)")
        )]
        [ValidateRange(0, 32767)]
        [int] $SilenceThreshold = 30,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Length penalty"
        )]
        [ValidateRange(0, 1)]
        [float] $LengthPenalty,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Entropy threshold"
        )]
        [ValidateRange(0, 1)]
        [float] $EntropyThreshold,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Log probability threshold"
        )]
        [ValidateRange(0, 1)]
        [float] $LogProbThreshold,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "No speech threshold"
        )]
        [ValidateRange(0, 1)]
        [float] $NoSpeechThreshold,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The type of LLM query"
        )]
        [ValidateSet(
            "SimpleIntelligence",
            "Knowledge",
            "Pictures",
            "TextTranslation",
            "Coding",
            "ToolUse"
        )]
        [string] $LLMQueryType = "ToolUse",
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("The model identifier or pattern to use for AI " +
                "operations")
        )]
        [string] $Model,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The LM Studio specific model identifier"
        )]
        [Alias("ModelLMSGetIdentifier")]
        [string] $HuggingFaceIdentifier,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("The maximum number of tokens to use in AI " +
                "operations")
        )]
        [int] $MaxToken,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("How much to offload to the GPU. If 'off', GPU " +
                "offloading is disabled. If 'max', all layers are " +
                "offloaded to GPU. If a number between 0 and 1, " +
                "that fraction of layers will be offloaded to the " +
                "GPU. -1 = LM Studio will decide how much to " +
                "offload to the GPU. -2 = Auto")
        )]
        [ValidateRange(-2, 1)]
        [int] $Gpu = -1,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Image detail level"
        )]
        [ValidateSet("low", "medium", "high")]
        [string] $ImageDetail = "low",
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The API endpoint URL for AI operations"
        )]
        [string] $ApiEndpoint,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The API key for authenticated AI operations"
        )]
        [string] $ApiKey,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The timeout in seconds for AI operations"
        )]
        [int] $TimeoutSeconds,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("A JSON schema for the requested output format")
        )]
        [string] $ResponseFormat = $null,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Will only output markup blocks of the " +
                "specified types")
        )]
        [ValidateNotNull()]
        [string[]] $MarkupBlocksTypeFilter = @(
            "json",
            "powershell",
            "C#",
            "python",
            "javascript",
            "typescript",
            "html",
            "css",
            "yaml",
            "xml",
            "bash"
        ),
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Database path for preference data files"
        )]
        [string] $PreferencesDatabasePath,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Array of PowerShell command definitions to use " +
                "as tools")
        )]
        [GenXdev.Helpers.ExposedCmdletDefinition[]]
        $ExposedCmdLets = @(),
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Include model's thoughts in output"
        )]
        [switch] $IncludeThoughts,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Include model's thoughts in output"
        )]
        [switch] $DontAddThoughtsToHistory,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Continue from last conversation"
        )]
        [switch] $ContinueLast,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Disable text-to-speech for AI responses"
        )]
        [switch] $DontSpeak,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Disable text-to-speech for AI thought responses"
        )]
        [switch] $DontSpeakThoughts,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Don't use silence detection to automatically " +
                "stop recording.")
        )]
        [switch] $NoVOX,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Whether to use desktop audio capture instead " +
                "of microphone input")
        )]
        [switch] $UseDesktopAudioCapture,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Don't use context"
        )]
        [switch] $NoContext,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Use beam search sampling strategy"
        )]
        [switch] $WithBeamSearchSamplingStrategy,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Whether to suppress reconized text in the " +
                "output")
        )]
        [switch] $OnlyResponses,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Don't store session in session cache"
        )]
        [switch] $NoSessionCaching,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Will only output markup block responses"
        )]
        [switch] $OutputMarkupBlocksOnly,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Show the LM Studio window"
        )]
        [switch] $ShowWindow,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Force stop LM Studio before initialization"
        )]
        [switch] $Force,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Use alternative settings stored in session " +
                "for AI preferences")
        )]
        [switch] $SessionOnly,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Clear alternative settings stored in session " +
                "for AI preferences")
        )]
        [switch] $ClearSession,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Store settings only in persistent preferences " +
                "without affecting session")
        )]
        [Alias("FromPreferences")]
        [switch] $SkipSession
        #######################################################################
    )

    begin {

        # copy identical parameter values for meta language configuration
        $params = GenXdev.Helpers\Copy-IdenticalParamValues `
            -BoundParameters $PSBoundParameters `
            -FunctionName "GenXdev.AI\Get-AIMetaLanguage" `
            -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable `
                -Scope Local `
                -ErrorAction SilentlyContinue)

        # determine appropriate language setting for audio recognition
        $Language = GenXdev.AI\Get-AIMetaLanguage @params

        # initialize stopping flag for chat loop
        $stopping = $false
        Microsoft.PowerShell.Utility\Write-Verbose (
            "Starting new audio LLM chat sessio")

        # handle exposed cmdlets configuration
        Microsoft.PowerShell.Utility\Write-Verbose (
            "Configuring exposed cmdlets...")
        if ($null -eq $ExposedCmdLets) {

            # check if continuing last session with cached cmdlets
            if ($ContinueLast -and $Global:LMStudioGlobalExposedCmdlets) {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Using existing exposed cmdlets from last session")
                $ExposedCmdLets = $Global:LMStudioGlobalExposedCmdlets
            }
            else {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Initializing default exposed cmdlets")
                # initialize default allowed PowerShell cmdlets
                $ExposedCmdLets = @(
                    @{
                        Name          = "Get-ChildItem"
                        AllowedParams = @(
                            "Path=string",
                            "Recurse=boolean",
                            "Filter=array",
                            "Include=array",
                            "Exclude=array",
                            "Force"
                        )
                        OutputText    = $false
                        Confirm       = $false
                        JsonDepth     = 3
                    },
                    @{
                        Name          = "Find-Item"
                        AllowedParams = @("SearchMask", "Pattern", "PassThru")
                        OutputText    = $false
                        Confirm       = $false
                        JsonDepth     = 3
                    },
                    @{
                        Name          = "Get-Content"
                        AllowedParams = @("Path=string")
                        OutputText    = $false
                        Confirm       = $false
                        JsonDepth     = 2
                    },
                    @{
                        Name          = "Approve-NewTextFileContent"
                        AllowedParams = @("ContentPath", "NewContent")
                        OutputText    = $false
                        Confirm       = $true
                        JsonDepth     = 2
                    },
                    @{
                        Name          = "Invoke-WebRequest"
                        AllowedParams = @(
                            "Uri=string",
                            "Method=string",
                            "Body",
                            "ContentType=string",
                            "Method=string",
                            "UserAgent=string"
                        )
                        OutputText    = $false
                        Confirm       = $false
                        JsonDepth     = 4
                    },
                    @{
                        Name          = "Invoke-RestMethod"
                        AllowedParams = @(
                            "Uri=string",
                            "Method=string",
                            "Body",
                            "ContentType=string",
                            "Method=string",
                            "UserAgent=string"
                        )
                        OutputText    = $false
                        Confirm       = $false
                        JsonDepth     = 99
                    },
                    @{
                        Name       = "UTCNow"
                        OutputText = $true
                        Confirm    = $false
                    },
                    @{
                        Name       = "Get-LMStudioModelList"
                        OutputText = $false
                        Confirm    = $false
                        JsonDepth  = 2
                    },
                    @{
                        Name       = "Get-LMStudioLoadedModelList"
                        OutputText = $false
                        Confirm    = $false
                        JsonDepth  = 2
                    },
                    @{
                        Name          = "Invoke-LLMQuery"
                        AllowedParams = @(
                            "Query",
                            "Model",
                            "Instructions",
                            "Attachments",
                            "IncludeThoughts"
                        )
                        ForcedParams  = @(
                            @{
                                Name = "NoSessionCaching";
                                Value = $true
                            }
                        )
                        OutputText    = $false
                        Confirm       = $false
                        JsonDepth     = 99
                    }
                )
            }
        }

        # cache exposed cmdlets if session caching is enabled
        if (-not $NoSessionCaching) {

            Microsoft.PowerShell.Utility\Write-Verbose (
                "Caching exposed cmdlets for future sessions")
            $Global:LMStudioGlobalExposedCmdlets = $ExposedCmdLets
        }

        Microsoft.PowerShell.Utility\Write-Verbose (
            "Successfully initialized with $($ExposedCmdLets.Count) " +
            "exposed cmdlets")

        # ensure required parameters are properly set
        Microsoft.PowerShell.Utility\Write-Verbose (
            "Validating and setting required parameters")

        # ensure required parameters exist
        if (-not $PSBoundParameters.ContainsKey("Model")) {

            $null = $PSBoundParameters.Add("Model", $Model)
        }

        # add hugging face identifier if model is specified
        if (-not $PSBoundParameters.ContainsKey("HuggingFaceIdentifier") -and
            $PSBoundParameters.ContainsKey("Model")) {

            $null = $PSBoundParameters.Add("HuggingFaceIdentifier",
                $HuggingFaceIdentifier)
        }

        # ensure continue last parameter is available
        if (-not $PSBoundParameters.ContainsKey("ContinueLast")) {

            $null = $PSBoundParameters.Add("ContinueLast", $ContinueLast)
        }

        # initialize lm studio if using localhost endpoint
        if ([string]::IsNullOrWhiteSpace($ApiEndpoint) -or
            $ApiEndpoint.Contains("localhost")) {

            $initializationParams = `
                GenXdev.Helpers\Copy-IdenticalParamValues `
                -BoundParameters $PSBoundParameters `
                -FunctionName 'GenXdev.AI\Initialize-LMStudioModel' `
                -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable `
                    -Scope Local `
                    -Name * `
                    -ErrorAction SilentlyContinue)

            $modelInfo = GenXdev.AI\Initialize-LMStudioModel `
                @initializationParams
            $Model = $modelInfo.identifier
        }

        # remove force parameter to prevent issues downstream
        if ($PSBoundParameters.ContainsKey("Force")) {

            $null = $PSBoundParameters.Remove("Force")
            $Force = $false
        }

        # remove show window parameter to prevent issues downstream
        if ($PSBoundParameters.ContainsKey("ShowWindow")) {

            $null = $PSBoundParameters.Remove("ShowWindow")
            $ShowWindow = $false
        }

        # remove chat once parameter if present
        if ($PSBoundParameters.ContainsKey("ChatOnce")) {

            $null = $PSBoundParameters.Remove("ChatOnce")
        }

        # ensure exposed cmdlets parameter is available
        if (-not $PSBoundParameters.ContainsKey("ExposedCmdLets")) {

            $null = $PSBoundParameters.Add("ExposedCmdLets", $ExposedCmdLets);
        }

        # track if we had an initial query
        $hadAQuery = -not [string]::IsNullOrEmpty($Query)
    }

    process {

        # set recognized text to initial query value
        [string] $recognizedText = $Query

        # main chat loop - continue until user stops
        while (-not $stopping) {

            # handle initial query vs subsequent voice input
            if ($hadAQuery) {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Processing initial query: $Query")

                # check if we should process the initial query
                if ($PSCmdlet.ShouldProcess(
                    "Process initial query: $Query",
                    "Process Query",
                    "New-LLMAudioChat")) {

                    $hadAQuery = $false
                    $Query = [string]::Empty

                    # remove query parameter to prevent reuse
                    if ($PSBoundParameters.ContainsKey("Query")) {

                        $null = $PSBoundParameters.Remove("Query")
                    }
                }
            }
            else {

                Microsoft.PowerShell.Utility\Write-Host (
                    "Press any key to start recording or Q to quit")

                try {

                    # prepare audio transcription parameters
                    Microsoft.PowerShell.Utility\Write-Verbose (
                        "Preparing audio transcription parameters")
                    $audioParams = GenXdev.Helpers\Copy-IdenticalParamValues `
                        -BoundParameters $PSBoundParameters `
                        -FunctionName "GenXdev.AI\Invoke-Whisper" `
                        -DefaultValues `
                            (Microsoft.PowerShell.Utility\Get-Variable `
                                -Scope Local `
                                -ErrorAction SilentlyContinue)

                    # configure and execute audio recording
                    Microsoft.PowerShell.Utility\Write-Verbose (
                        "Configuring audio settings")
                    $audioParams.VOX = -not $NoVOX
                    $audioParams.Temperature = $AudioTemperature

                    # process text input or start recording
                    $recognizedText = $Query ? $Query.Trim() : [string]::Empty

                    # start audio recording if no text input provided
                    if ([string]::IsNullOrWhiteSpace($recognizedText)) {

                        Microsoft.PowerShell.Utility\Write-Verbose (
                            "Starting audio recording session")
                        $recognizedText = GenXdev.AI\Invoke-Whisper @audioParams
                    }
                }
                catch {

                    # handle audio recording errors
                    if ("$PSItem" -notlike "*aborted*") {

                        Microsoft.PowerShell.Utility\Write-Error $PSItem
                    }
                    Microsoft.PowerShell.Utility\Write-Verbose (
                        "Audio transcription failed or was aborted")
                    $Query = [string]::Empty
                    $recognizedText = [string]::Empty
                    continue
                }
            }

            # process recognized input if not empty
            if (-not [string]::IsNullOrWhiteSpace($recognizedText)) {

                $question = $recognizedText
                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Processing recognized input: $question")

                # prepare lm studio query parameters
                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Preparing LM Studio parameters")
                $invokeLMStudioParams = `
                    GenXdev.Helpers\Copy-IdenticalParamValues `
                    -BoundParameters $PSBoundParameters `
                    -FunctionName "GenXdev.AI\New-LLMTextChat" `
                    -DefaultValues `
                        (Microsoft.PowerShell.Utility\Get-Variable `
                            -Scope Local `
                            -ErrorAction SilentlyContinue)

                # configure and execute lm studio query
                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Configuring LM Studio query parameters")
                $invokeLMStudioParams.Query = $question
                $invokeLMSTudioParams.Speak = -not $DontSpeak
                $invokeLMStudioParams.SpeakThoughts = -not $DontSpeakThoughts
                $invokeLMStudioParams.ChatOnce = $true

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Executing LM Studio query")

                # execute the llm query if user confirms
                if ($PSCmdlet.ShouldProcess(
                    "Execute LM Studio query: $question",
                    "Query LM Studio",
                    "New-LLMAudioChat")) {

                    $null = GenXdev.AI\New-LLMTextChat @invokeLMStudioParams
                }

                Microsoft.PowerShell.Utility\Write-Host (
                    "Press any key to interrupt and start recording or Q to quit")
            }
            else {

                Microsoft.PowerShell.Utility\Write-Host (
                    "Too short or only silence recorded`r`n")
            }

            # monitor for key presses during speech output
            Microsoft.PowerShell.Utility\Write-Verbose (
                "Monitoring for key presses during speech output")
            $continueWaiting = $true
            while ($continueWaiting -and (GenXdev.Console\Get-IsSpeaking)) {

                # check for available key presses
                while ([Console]::KeyAvailable) {

                    $key = [Console]::ReadKey().Key
                    [System.Console]::Write("`e[1G`e[2K")

                    # quit if q key pressed
                    if ($key -eq [ConsoleKey]::Q) {

                        GenXdev.Console\Stop-TextToSpeech
                        Microsoft.PowerShell.Utility\Write-Host (
                            "---------------")
                        $continueWaiting = $false
                        $stopping = $true
                        return
                    }
                    else {

                        $continueWaiting = $false
                        break
                    }
                }

                # wait briefly before checking again
                $null = Microsoft.PowerShell.Utility\Start-Sleep `
                    -Milliseconds 100
            }

            # clear previous prompt
            [System.Console]::Write("`e[1A`e[2K")

            # stop text to speech and show separator
            GenXdev.Console\Stop-TextToSpeech
            Microsoft.PowerShell.Utility\Write-Host "---------------"
        }
    }

    end {

        Microsoft.PowerShell.Utility\Write-Verbose (
            "Audio chat session completed")
    }
}
###############################################################################