Functions/GenXdev.AI/New-LLMAudioChat.ps1
|
<############################################################################## Part of PowerShell module : GenXdev.AI Original cmdlet filename : New-LLMAudioChat.ps1 Original author : René Vaessen / GenXdev Version : 3.28.2026 ################################################################################ Copyright (c) 2026 René Vaessen / GenXdev Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ################################################################################> ############################################################################### <# .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 including window management, GPU acceleration, and advanced audio processing features. .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 LanguageIn Language to detect in audio input. Default: "English" .PARAMETER CpuThreads Number of CPU threads to use. 0=auto. Default: 0 .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 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 OutputMarkdownBlocksOnly Will only output markup block responses .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 .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 = 'Whisper model type to use, defaults to LargeV3Turbo' )] [ValidateSet('Tiny', 'TinyEn', 'Base', 'BaseEn', 'Small', 'SmallEn', 'Medium', 'MediumEn', 'LargeV1', 'LargeV2', 'LargeV3', 'LargeV3Turbo')] [string] $ModelType = 'SmallEn', ####################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Temperature for audio input recognition (0.0-1.0)' )] [ValidateRange(0.0, 1.0)] [double] $AudioTemperature = 0.5, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Temperature for response randomness (0.0-1.0)' )] [ValidateRange(-1, 1.0)] [double] $Temperature = -1, ####################################################################### [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' )] [Alias('Language')] [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] $LanguageIn, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ('Number of CPU threads to use, defaults to 0 ' + '(auto)') )] [int] $CpuThreads = 0, ####################################################################### [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 = '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 = 'Indicates that LLM has no support for JSON schemas' )] [string] $NoSupportForJsonSchema, ################################################################### [Parameter( Mandatory = $false, HelpMessage = ('Shows tool chain invocations in console') )] [switch] $ShowToolChainInvocations, ####################################################################### [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' )] [Alias('DatabasePath')] [string] $PreferencesDatabasePath, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ('Array of PowerShell command definitions to use ' + 'as tools') )] [GenXdev.Helpers.ExposedCmdletDefinition[]] $ExposedCmdLets = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'ScriptBlock array of tool functions to expose to the LLM (pass-through to Invoke-LLMQuery)' )] [ScriptBlock[]] $Functions, ############################################################################### [Alias('NoConfirmationFor')] [Parameter( Mandatory = $false, HelpMessage = 'Array of tool function names that do not require confirmation (pass-through to Invoke-LLMQuery)' )] [string[]] $NoConfirmationToolFunctionNames, ############################################################################### [Alias('chat')] [Parameter( Mandatory = $false, HelpMessage = 'Chat mode for LLM query (pass-through to Invoke-LLMQuery)' )] [string] $ChatMode, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Maximum tool call back length (pass-through to Invoke-LLMQuery)' )] [int] $MaxToolcallBackLength, ####################################################################### [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 = 'Audio device name or GUID (supports wildcards, picks first match)' )] [string] $AudioDevice, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = "Use both desktop and recording device" )] [switch] $UseDesktopAndRecordingDevice, ####################################################################### [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] $OutputMarkdownBlocksOnly, ####################################################################### [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 { # initialize stopping flag for chat loop $stopping = $false Microsoft.PowerShell.Utility\Write-Verbose ( 'Starting new audio LLM chat session') # 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:LLMGlobalExposedCmdlets) { Microsoft.PowerShell.Utility\Write-Verbose ( 'Using existing exposed cmdlets from last session') $ExposedCmdLets = $Global:LLMGlobalExposedCmdlets } 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 = 'Microsoft.PowerShell.Utility\Invoke-WebRequest' AllowedParams = @( 'Uri=string', 'Method=string', 'Body', 'ContentType=string', 'Method=string', 'UserAgent=string' ) OutputText = $true Confirm = $false JsonDepth = 6 }, @{ 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 = '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:LLMGlobalExposedCmdlets = $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) } # ensure continue last parameter is available if (-not $PSBoundParameters.ContainsKey('ContinueLast')) { $null = $PSBoundParameters.Add('ContinueLast', $ContinueLast) } # 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 { # display appropriate message based on VOX setting if ($NoVOX) { Microsoft.PowerShell.Utility\Write-Host ( 'Press any key to start recording or Q to quit') # wait for key press when VOX is disabled $keyPressed = [Console]::ReadKey($true) if ($keyPressed.Key -eq [ConsoleKey]::Q) { # Q pressed - wait for potential second Q Microsoft.PowerShell.Utility\Write-Host 'Q pressed - press Q again within 1000ms to quit...' $startTime = Microsoft.PowerShell.Utility\Get-Date $waitForSecondQ = $true while ($waitForSecondQ -and ((Microsoft.PowerShell.Utility\Get-Date) - $startTime).TotalMilliseconds -lt 1000) { if ([Console]::KeyAvailable) { $secondKey = [Console]::ReadKey($true) if ($secondKey.Key -eq [ConsoleKey]::Q) { Microsoft.PowerShell.Utility\Write-Host 'Double Q detected - exiting audio chat' $stopping = $true while ([Console]::KeyAvailable) { [Console]::ReadKey($true) | Microsoft.PowerShell.Core\Out-Null } return } else { $waitForSecondQ = $false break } } Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds 10 } if ($waitForSecondQ) { Microsoft.PowerShell.Utility\Write-Host 'No second Q detected - continuing to recording' } } } else { Microsoft.PowerShell.Utility\Write-Host ( 'Start speaking (voice activated) or press Q twice to quit') # brief check for immediate Q press to quit before starting VOX if ([Console]::KeyAvailable) { $keyPressed = [Console]::ReadKey($true) if ($keyPressed.Key -eq [ConsoleKey]::Q) { # Q pressed - wait for potential second Q Microsoft.PowerShell.Utility\Write-Host 'Q pressed - press Q again within 1000ms to quit...' $startTime = Microsoft.PowerShell.Utility\Get-Date $waitForSecondQ = $true while ($waitForSecondQ -and ((Microsoft.PowerShell.Utility\Get-Date) - $startTime).TotalMilliseconds -lt 1000) { if ([Console]::KeyAvailable) { $secondKey = [Console]::ReadKey($true) if ($secondKey.Key -eq [ConsoleKey]::Q) { Microsoft.PowerShell.Utility\Write-Host 'Double Q detected - exiting audio chat' $stopping = $true while ([Console]::KeyAvailable) { [Console]::ReadKey($true) | Microsoft.PowerShell.Core\Out-Null } return } else { $waitForSecondQ = $false break } } Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds 10 } if ($waitForSecondQ) { Microsoft.PowerShell.Utility\Write-Host 'No second Q detected - continuing to recording' } } } } try { # prepare audio transcription parameters Microsoft.PowerShell.Utility\Write-Verbose ( 'Preparing audio transcription parameters') $audioParams = GenXdev\Copy-IdenticalParamValues ` -BoundParameters $PSBoundParameters ` -FunctionName 'GenXdev\Start-AudioTranscription' ` -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\Start-AudioTranscription @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 llm query parameters Microsoft.PowerShell.Utility\Write-Verbose ( 'Preparing LLM parameters') $invokeLLMQueryParams = ` GenXdev\Copy-IdenticalParamValues ` -BoundParameters $PSBoundParameters ` -FunctionName 'GenXdev\New-LLMTextChat' ` -DefaultValues ` (Microsoft.PowerShell.Utility\Get-Variable ` -Scope Local ` -ErrorAction SilentlyContinue) # configure and execute LLM query Microsoft.PowerShell.Utility\Write-Verbose ( 'Configuring LLM query parameters') $invokeLLMQueryParams.Query = $question $invokeLLMQueryParams.Speak = -not $DontSpeak $invokeLLMQueryParams.SpeakThoughts = -not $DontSpeakThoughts $invokeLLMQueryParams.ChatOnce = $true Microsoft.PowerShell.Utility\Write-Verbose ( 'Executing LLM query') # execute the llm query if user confirms if ($PSCmdlet.ShouldProcess( "Execute LLM query: $question", 'Query LLM', 'New-LLMAudioChat')) { # capture and display the LLM response in cyan $response = GenXdev\New-LLMTextChat @invokeLLMQueryParams if (-not [string]::IsNullOrWhiteSpace($response)) { Microsoft.PowerShell.Utility\Write-Host -ForegroundColor Cyan $response } } 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\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\Stop-TextToSpeech Microsoft.PowerShell.Utility\Write-Host 'Q pressed - press Q again within 1000ms to quit or any other key to continue...' # wait 200ms for potential second Q press $qPressTime = Microsoft.PowerShell.Utility\Get-Date $waitingForSecondQ = $true while ($waitingForSecondQ -and ((Microsoft.PowerShell.Utility\Get-Date) - $qPressTime).TotalMilliseconds -lt 1000) { if ([Console]::KeyAvailable) { $secondKey = [Console]::ReadKey($true) if ($secondKey.Key -eq [ConsoleKey]::Q) { # double Q pressed - exit completely Microsoft.PowerShell.Utility\Write-Host 'Double Q detected - exiting audio chat' Microsoft.PowerShell.Utility\Write-Host '---------------' $continueWaiting = $false $stopping = $true while ([Console]::KeyAvailable) { [Console]::ReadKey($true) | Microsoft.PowerShell.Core\Out-Null } return } else { # other key pressed - just continue current loop iteration Microsoft.PowerShell.Utility\Write-Host 'Continuing...' $waitingForSecondQ = $false $continueWaiting = $false break } } Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds 10 } # if we reach here without second Q, just continue the current loop iteration if ($waitingForSecondQ) { Microsoft.PowerShell.Utility\Write-Host 'No second Q detected - continuing...' } Microsoft.PowerShell.Utility\Write-Host '---------------' $continueWaiting = $false break } 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\Stop-TextToSpeech Microsoft.PowerShell.Utility\Write-Host '---------------' } } end { Microsoft.PowerShell.Utility\Write-Verbose ( 'Audio chat session completed') } } ############################################################################### |