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"
# )
# }
# }