Public/Show-OAILocalPlayground.ps1
# <# # .SYNOPSIS # Starts a local playground interface that lets you play with OAI # #> # function Show-OAILocalPlayground { # param( # $Port = 8080, # $Threads = 2, # [switch]$DisableLaunch # ) # $script:port = $port # if (-not $ENV:OpenAIKey) { # Write-Warning 'No $ENV:OpenAIKey found.' # } # # When someone clones the PowerShellAIAssistant repo, but it isn't in any PSModulePath, the playground will fail. # if (-not (Get-Module -list PowerShellAIAssistant)) { # throw 'PowerShellAIAssistant not found in any module directories. Please add a PSModulePath to the correct location if using a custom one. e.g. $ENV:PSModulePath += "; $pwd"' # } # # This is almost like a proxy function since it is directly stolen from Pode.Web and then edited so that # # ConvertTo-PodeWebPage works with advanced parameter blocks on pwsh 7.4+ # # Also has a slight improvement to use ConvertTo-Json for output results. # # Likely remove this function as soon as Pode.Web >v0.8.3 is released. # function Convertto-PodeWebPage { # [CmdletBinding()] # param( # [Parameter(ValueFromPipeline = $true)] # [string[]] # $Commands, # [Parameter()] # [string] # $Module, # [switch] # $GroupVerbs, # [Parameter()] # [Alias('NoAuth')] # [switch] # $NoAuthentication # ) # # if a module was supplied, import it - then validate the commands # if (![string]::IsNullOrWhiteSpace($Module)) { # Import-PodeModule -Name $Module # Export-PodeModule -Name $Module # Write-Verbose "Getting exported commands from module" # $ModuleCommands = (Get-Module -Name $Module | Sort-Object -Descending | Select-Object -First 1).ExportedCommands.Keys # # if commands were supplied validate them - otherwise use all exported ones # if (Test-PodeIsEmpty $Commands) { # Write-Verbose "Using all commands in $($Module) for converting to Pages" # $Commands = $ModuleCommands # } # else { # Write-Verbose "Validating supplied commands against module's exported commands" # foreach ($cmd in $Commands) { # if ($ModuleCommands -inotcontains $cmd) { # throw "Module $($Module) does not contain function $($cmd) to convert to a Page" # } # } # } # } # # if there are no commands, fail # if (Test-PodeIsEmpty $Commands) { # throw 'No commands supplied to convert to Pages' # } # $sysParams = [System.Management.Automation.PSCmdlet]::CommonParameters.GetEnumerator() | ForEach-Object { $_ } # # create the pages for each of the commands # foreach ($cmd in $Commands) { # Write-Verbose "Building page for $($cmd)" # $cmdInfo = (Get-Command -Name $cmd -ErrorAction Stop) # $sets = $cmdInfo.ParameterSets # if (($null -eq $sets) -or ($sets.Length -eq 0)) { # continue # } # # for cmdlets this will be null # $ast = $cmdInfo.ScriptBlock.Ast # $paramDefs = $null # if ($null -ne $ast) { # $paramDefs = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.ParameterAst] }, $true) | Where-Object { # $_.Parent.Parent.Parent.Name -ieq $cmd # } # } # $tabs = New-PodeWebTabs -Tabs @(foreach ($set in $sets) { # $elements = @(foreach ($param in $set.Parameters) { # if ($sysParams -icontains $param.Name) { # continue # } # $type = $param.ParameterType.Name # $default = $null # if ($null -ne $paramDefs) { # $default = ($paramDefs | Where-Object { $_.DefaultValue -and $_.Name.Extent.Text -ieq "`$$($param.Name)" }).DefaultValue.Value # } # if ($type -iin @('boolean', 'switchparameter')) { # New-PodeWebCheckbox -Name $param.Name -AsSwitch # } # else { # switch ($type) { # 'pscredential' { # New-PodeWebCredential -Name $param.Name # } # default { # $multiple = $param.ParameterType.Name.EndsWith('[]') # if ($param.Attributes.TypeId.Name -icontains 'ValidateSetAttribute') { # $values = ($param.Attributes | Where-Object { $_.TypeId.Name -ieq 'ValidateSetAttribute' }).ValidValues # New-PodeWebSelect -Name $param.Name -Options $values -SelectedValue $default -Multiple:$multiple # } # elseif ($param.ParameterType.BaseType.Name -ieq 'enum') { # $values = [enum]::GetValues($param.ParameterType) # New-PodeWebSelect -Name $param.Name -Options $values -SelectedValue $default -Multiple:$multiple # } # else { # New-PodeWebTextbox -Name $param.Name -Value $default # } # } # } # } # }) # $elements += (New-PodeWebHidden -Name '_Function_Name_' -Value $cmd) # $name = $set.Name # if ([string]::IsNullOrWhiteSpace($name) -or ($set.Name -iin @('__AllParameterSets'))) { # $name = 'Default' # } # $formId = "form_param_$($cmd)_$($name)" # $form = New-PodeWebForm -Name Parameters -Id $formId -Content $elements -AsCard -NoAuthentication:$NoAuthentication -ScriptBlock { # $cmd = $WebEvent.Data['_Function_Name_'] # $WebEvent.Data.Remove('_Function_Name_') # $_args = @{} # foreach ($key in $WebEvent.Data.Keys) { # if ($key -imatch '(?<name>.+)_(Username|Password)$') { # $name = $Matches['name'] # $uKey = "$($name)_Username" # $pKey = "$($name)_Password" # if (![string]::IsNullOrWhiteSpace($WebEvent.Data[$uKey]) -and ![string]::IsNullOrWhiteSpace($WebEvent.Data[$pKey])) { # $creds = (New-Object System.Management.Automation.PSCredential -ArgumentList $WebEvent.Data[$uKey], (ConvertTo-SecureString -AsPlainText $WebEvent.Data[$pKey] -Force)) # $_args[$name] = $creds # } # } # else { # if ($WebEvent.Data[$key] -iin @('true', 'false')) { # $_args[$key] = ($WebEvent.Data[$key] -ieq 'true') # } # else { # if ($WebEvent.Data[$key].Contains(',')) { # $_args[$key] = ($WebEvent.Data[$key] -isplit ',' | ForEach-Object { $_.Trim() }) # } # else { # $_args[$key] = $WebEvent.Data[$key] # } # } # } # } # try { # (. $cmd @_args) | ConvertTo-Json | Out-PodeWebTextbox -Multiline -Preformat # } # catch { # $_.Exception | ConvertTo-Json -Depth 0 | Out-PodeWebTextbox -Multiline -Preformat # } # } # New-PodeWebTab -Name $name -Layouts $form # }) # $group = [string]::Empty # if ($GroupVerbs) { # $group = $cmdInfo.Verb # if ([string]::IsNullOrWhiteSpace($group)) { # $group = '_' # } # } # Add-PodeWebPage -Name $cmd -Icon Settings -Layouts $tabs -Group $group -NoAuthentication:$NoAuthentication # } # } # # Avoid the ugly logic of (-not () -or -not ()) # if ((Get-Module -list Pode) -and (Get-Module -list Pode.Web)) {} # else { # # Get enthusiastic consent before installing extra modules on someone's system # if ("y" -eq (Read-Host "The local playground requires Pode and Pode.Web. Type 'y' to install to local user from PSGallery")) { # if (-not (Get-Module -list Pode)) { Install-Module Pode -Scope CurrentUser -Force } # if (-not (Get-Module -list Pode.Web)) { Install-Module Pode.Web -Scope CurrentUser -Force } # } # else { # throw "Pode and Pode.Web are not installed and consent was not given." # } # } # # Pode.Web needs to be in the global scope for runspaces to access functions # Import-Module Pode.Web -Scope Global # Start-PodeServer -Browse:(-not $DisableLaunch) -StatusPageExceptions Show -Threads $Threads { # $endpoint_param = @{ # Address = "localhost" # Port = $script:Port # Protocol = "Http" # Name = "Local Playground" # } # Add-PodeEndpoint @endpoint_param # # Enable sessions so that user data can be stored server side easily # Enable-PodeSessionMiddleware -Duration ([int]::MaxValue) # Use-PodeWebTemplates -Title SampleApp -Theme Dark # # You want the assistants and history to be accessible regardless of session # New-PodeLockable "historyList_lock" # Set-PodeState -Name "historyList" -Value @() # New-PodeLockable "assistantList_lock" # Set-PodeState -Name "assistantList" -Value @() # Lock-PodeObject -Name "historyList_lock" -ScriptBlock { # Set-PodeState -Name "historyList" -Value ([array](Get-PodeState -Name "historyList") + "Get-OAIAssistant") # } # $assistantList = Get-OAIAssistant # Lock-PodeObject -Name "assistantList_lock" -ScriptBlock { # Set-PodeState -Name "assistantList" -Value $assistantList # } # Add-PodeWebPage -Name CodeHistory -DisplayName "Code history" -ScriptBlock { # New-PodeWebCard -Content @( # New-PodeWebTextbox -Name "Cmdlets" -Multiline -ReadOnly -Value (Get-History).CommandLine # ) # } # # This page will create a conversation with an assistant, but does not use the boilerplate cmdlets # # included in the PowerShellAIAssistant module like New-OAIThreadQuery because it doesn't save any # # actual calls to remote resources and doesn't as easily allow for follow up messages # Add-PodeWebPage -Name ChatGPT -ScriptBlock { # # Provide an all-in-one interface to make it as easy as possible to stay on this page # New-PodeWebCard -Content @( # New-PodeWebButton -Name "Update assistant list" -ScriptBlock { # $assistantList = Get-OAIAssistant # Lock-PodeObject -Name "historyList_lock" -ScriptBlock { # Set-PodeState -Name "historyList" -Value ([array](Get-PodeState -Name "historyList") + "Get-OAIAssistant") # } # Lock-PodeObject -Name "assistantList_lock" -ScriptBlock { # Set-PodeState -Name "assistantList" -Value $assistantList # } # Move-PodeWebUrl -Url /pages/ChatGPT # } # ) -CssStyle @{"max-width" = "800px" } # New-PodeWebCard -Content @( # New-PodeWebForm -Name 'Example' -Content @( # New-PodeWebSelect -Name "Assistant" -Id "Assistant" -Options (@((Get-PodeState -Name "assistantList").id) + "New") -DisplayOptions (@((Get-PodeState -Name "assistantList").Name) + "New (Sample assistant)") # New-PodeWebSelect -Name "Thread" -Id "Thread" -Options (@($WebEvent.Session.Data.Threads.id) + "New...") -SelectedValue $WebEvent.Session.Data.Thread.id # New-PodeWebTextbox -Name 'Message' # ) -ScriptBlock { # if ($WebEvent.Data['Assistant'] -eq "New") { # $assistant = New-OAIAssistant -Name 'Math Tutor' -Instructions 'You are a helpful math assistant. Please explain your answers.' # Lock-PodeObject -Name "assistantList_lock" -ScriptBlock { # $assistantList = Get-PodeState -Name "assistantList" # $assistantList = @($assistantList) + $assistant # Set-PodeState -Name "assistantList" -Value $assistantList # } # Update-PodeWebSelect -Id "Assistant" -SelectedValue $assistant.id -Options (@((Get-PodeState -Name "assistantList").id) + "New") -DisplayOptions (@((Get-PodeState -Name "assistantList").Name) + "New (Sample assistant)") # $assistantID = $assistant.id # } # else { # $assistantID = $WebEvent.Data['Assistant'] # } # if ($WebEvent.Data['Thread'] -eq "New...") { # $WebEvent.Session.Data.Thread = New-OAIThread # $WebEvent.Session.Data.Threads = @($WebEvent.Session.Data.Threads) + $WebEvent.Session.Data.Thread | Sort-Object -Unique -Property created_at # @($WebEvent.Session.Data.Threads.id) + "New..." | Update-PodeWebSelect -Id "Thread" -SelectedValue $WebEvent.Session.Data.Thread # } # else { # $WebEvent.Session.Data.Thread = $WebEvent.Session.Data.Threads | Where-Object id -EQ $WebEvent.Data['Thread'] # } # New-OAIMessage -ThreadId $WebEvent.Session.Data.Thread.id -Role user -Content $WebEvent.Data['Message'] | Out-Null # $run = New-OAIRun -ThreadId $WebEvent.Session.Data.Thread.id -AssistantId $assistantID # Wait-OAIOnRun -Run $run -Thread $WebEvent.Session.Data.Thread | Out-Null # $messages = Get-OAIMessage -ThreadId $WebEvent.Session.Data.Thread.id -Order asc # $messages.data | Select-Object Role, @{n = 'Message'; e = { $_.content.text.value } } | ForEach-Object { # "{0}: {1}" -f $_.Role, $_.Message # if ($_.Role -eq "Assistant") { "" } # } | Out-String | Out-PodeWebTextbox -Multiline -ReadOnly # Clear-PodeWebTextbox -Name Message # } # ) -CssStyle @{"max-width" = "800px" } # } # ConvertTo-PodeWebPage -Module PowerShellAIAssistant -Commands @( # "Get-OAIAssistant" # "New-OAIAssistant" # # "New-OAIThread" # # "New-OAIMessage" # "New-OAIRun" # # "Wait-OAIOnRun" # "Get-OAIMessage" # "Remove-OAIThread" # "Remove-OAIAssistant" # ) # } # } |