diff --git a/aidoc.ps1 b/aidoc.ps1 new file mode 100644 index 0000000..02eba82 --- /dev/null +++ b/aidoc.ps1 @@ -0,0 +1,246 @@ +<# + Uma pequeno dilema meu: Um produto ou serviço só muito bom, se ele consegue suprir as demandas dos próprios criados! + E, baseado nisso, temos o script AI DOC! + + ESte script usa o próprio PowershAI para gerar docs em outros idiomas + ele é um utilitário para facilitar porta a documentação original para outros idiomas, que ainda não possuem uma tradução revisada por alguém expert no idioma de destino. + + Ele pode ser utilizado sempre que qusier gerar novos arquivos de documentação, ou atualizar existentes. + Para manter a qualidade, recomendamos o uso com LLM avançados, como GPT-4o-mini, llam 3.1, ou gemini flash. + Importante que o modelo suporte longos contextos! + + O script causa atualizações de arquivos no projeto, então, é preciso seguir o fluxo normalmente: + - commit no git + - bump de versão + - publicar + + Imagine que este script seja seu assistente de tradução do PowershAI! + Use e abuse! +#> +param( + $SourceLang = $null + ,$TargetLang = $null + ,[switch]$KeepProvider + ,$Provider = "openai" +) + +$ErrorActionPreference = "Stop"; + +function JoinPath { + return ($Args -Join [IO.Path]::DirectorySeparatorChar) +} + +function JoinPath { + return ($Args -Join [IO.Path]::DirectorySeparatorChar) +} + +$ProjectRoot = $PSScriptRoot; + +write-host "Loading local powershai module"; +import-module -force (JoinPath $ProjectRoot powershai) + +if(!$SourceLang){ + throw "Must specifuy -SourceLang" +} + +if(!$TargetLang){ + throw "Must specifuy -TargetLang" +} + +$SourcePath = JoinPath $ProjectRoot docs $SourceLang; +$TargetPath = JoinPath $ProjectRoot docs $TargetLang + +if(-not(Test-Path $SourcePath)){ + throw "Invalid Lang: $SourceLang, PathNotfound = $SourcePath"; +} + +if(-not(Test-Path $TargetPath)){ + throw "Invalid Lang: $TargetLang, PathNotfound = $TargetPath"; +} + + +$TranslationMapFile = JoinPath $TargetPath AiTranslations.json + +if(Test-Path $TranslationMapFile){ + $TranslationMap = Get-Content -Raw $TranslationMapFile | ConvertFrom-Json; +} else { + $TranslationMap = @{} +} + +# List all source files! +$SourceFiles = gci -rec (JoinPath $sourcePath *.md) + +$NewMap = @{}; + +if(!$KeepProvider){ + write-host "Enforcing provider $Provider"; + Set-AiProvider $provider; +} + + + + +foreach($SrcFile in $SourceFiles){ + + $SrcRelPath = $SrcFile.FullName.replace($SourcePath,'') -replace '^.','' + $FileId = $SrcRelPath.replace('\','/'); + + $TargetFilePath = JoinPath $TargetPath $SrcRelPath; + $TargetFile = Get-Item $TargetFilePath -EA SilentlyContinue; + + write-host "File: $SrcRelPath"; + + write-host " Calculating Hash..." + $SrcHash = Get-FileHash $SrcFile; + + $TranslationInfo = $TranslationMap.$FileId; + + $TargetHash = $null + $FileBackup = $null + if($TargetFile){ + write-host " Target exists!" + $FileBackup = Get-Content $TargetFile; + + # check if file is auto updated! + # aCalculate hash! + $TargetHash = Get-FileHash $TargetFile + + # Checa se o arquivo foi geraod automaticamente! + if(!$TranslationInfo){ + write-host " File not generated by AiDoc! Skipping..."; + continue; + } + + # Get hash! + $AiHash = $TranslationInfo.TargetHash; + + if($AiHash -ne $TargetHash.Hash){ + write-host " FileChanged. Will not be updated..."; + continue; + } + + # Neste ponto, assume que o arquivo pode ser atualizavel!s + } + + #Atualiza o map! + #Mesmo que nao haja atualizcoes nessa iteracao, garantimos que os dados serão escrito na proxima vez em que o arquivo de map for atualziado! + #Se ele nunca for atualizado, então, não tem problema, significa que o valor antigo não foi sobrescrito! + $NewMap[$FileId] = $TranslationInfo; + + # Checa se mudou! + if($TranslationInfo.SrcHash -eq $SrcHash.Hash){ + write-host " Nothing changed... Skipping"; + continue; + } + + + write-host " Translating file..."; + $FileContent = Get-Content -Raw $SrcFile; + + # Divide o markdown em blocos, para evitar ultrassar o size do modelo! + $BlockSize = 6000; + $FullTranslation = ""; + + $Control = @{ + buffer = @() + TotalChars = 0 + text = "" + } + + Function Translate { + $BufferText = $Control.buffer -Join "`n"; + $Control.buffer = @(); + $Control.TotalChars = 0; + + write-host " Buffer:" $BufferText.length; + + $system = @( + "Traduza o texto do usuário para a linguagem de código $($TargetLang). Retorne APENAS o texto traduzido." + "Manter o conteúdo original entre . Traduzir comentários de código, nomes de funções de exemplo. Não traduzir nomes de comandos do PowershAI." + ) -Join "`n" + + $prompt = @( + "s: $system" + $BufferText + ) + + write-host " Invoking AI..." + $result = Get-AiChat -prompt $prompt + $translated = $result.choices[0].message.content; + write-host " Translated: $($translated.length) chars" + $Control.text += $translated + } + + if($FileContent.length -gt $BlockSize){ + $FileLines = $FileContent -split "`r?`n" + foreach($line in $FileLines){ + + $LineLen = $Line.length; + $EstTotal = $Control.TotalChars + $LineLen; + + if($EstTotal -ge $BlockSize){ + Translate + } + + $Control.buffer += $line; + $Control.TotalChars += $LineLen; + } + } else { + $Control.buffer = $FileContent; + } + + Translate + + $Translated = $Control.text + write-host " Translated Length: $($Translated.length)"; + + write-host " Creating target directories..." + $Paths = Split-Path $TargetFilePath -Parent; + $null = New-Item -ItemType Directory -Path $Paths -force; + + + + try { + write-host " Updating target content..." + $Translated | Set-Content -Encoding UTF8 -Path $TargetFilePath + + # check if file is auto updated! + # aCalculate hash! + $TargetNewHash = Get-FileHash $TargetFilePath + + write-host " Creating new map..." + $NewMap[$FileId] = @{ + SrcHash = $SrcHash.Hash + TargetHash = $TargetNewHash.Hash; + } + + write-host " Updating $TranslationMapFile"; + $TranslationMapJson = $NewMap | ConvertTo-Json + Set-Content -Path $TranslationMapFile -Value $TranslationMapJson + } catch { + # Restore file backup! + if($FileBackup){ + write-warning " Restoring file backup due to errors..." + $FileBackup | Set-Content -Encoding UTF8 -Path $TargetFilePath + } + + throw; + } + + +} + + + + + + + + + + + + + + + diff --git a/powershai/powershai.psm1 b/powershai/powershai.psm1 index ef02d94..f2587f7 100644 --- a/powershai/powershai.psm1 +++ b/powershai/powershai.psm1 @@ -111,6 +111,45 @@ function SetType($obj, $name){ } } +# retorna a lista de parametros do caller! +function GetMyParams(){ + + $cmd = Get-PSCallStack + $Caller = $cmd[1]; + + $CmdParams = @($Caller.InvocationInfo.MyCommand.Parameters.values) + + $ParamList = @{}; + + function GetCommonParams + { + [CmdletBinding()] + param() + $MyInvocation.MyCommand.Parameters.Keys + } + + $CommonParams = GetCommonParams + + foreach($Param in $CmdParams){ + if($Caller.InvocationInfo.MyCommand.CmdletBinding){ + if($Param.name -in $CommonParams){ + continue; + } + } + + $ParamList[$Param.name] = Get-Variable -Name $Param.name -Scope 1 -ValueOnly -EA SilentlyContinue + + } + + + + return @{ + bound = $ParamList + args = $Caller.InvocationInfo.UnboundArguments + caller = $Caller + } +} + <# .DESCRIPTION FAciltia a criação de exceptions customizadas! @@ -1297,6 +1336,16 @@ function New-PowershaiParameters { #O provider deve implementar o suporte a esse. #Para usá-lo você deve saber os detalhes de implementação do provider e como a API dele funciona! $RawParams = @{} + + + ,# Controla o template usado ao injetar dados de contexto! + # Este parâmetro é um scriptblock que deve retornar uma string com o contexto a ser injetado no prompt! + # Os parâmetros do scriptblock são: + # FormattedObject - O objeto que representa o chat ativo, já formatado com o Formatter configurado + # CmdParams - Os parâmetros passados para Send-PowershaAIChat. É o mesmo objeto retorndo por GetMyParams + # Chat - O chat no qual os dados estão sendo enviados. + # Se nulo, irá gerar um default. Verifique o cmdlet Send-PowershaiChat para detalhes + $ContextFormat = $null ) # Get the command name @@ -1770,14 +1819,26 @@ function ConvertTo-PowershaiContextTable { $POWERSHAI_FORMATTERS_SHORTCUTS['table'] = 'ConvertTo-PowershaiContextTable' - +<# + .DESCRIPTION + Formato um objeto para ser injetado no contexto! + Este cmdlet é usado pelo Send-PowershaiChat quando o contexto é ativado. + Você pode invocá-lo diretamente caso queira manualmente injetar dados e/ou debugar o processo! +#> function Format-PowershaiContext { [CmdletBinding()] param( - $obj - ,$params - ,$func - ,$ChatId = "." + #Objeto qualquer a ser injetado + $obj + + ,#Parâmetro a ser passado para a função formatter + $params + + ,#Função q ser nivocada. Se não especificado usa o defualt do chat. + $func + + ,#Chat em qual operar + $ChatId = "." ) write-verbose "Getting ChatId..." @@ -1816,13 +1877,34 @@ function Format-PowershaiContext { } <# + .SYNOPSIS + Envia uma mensagem em um Chat do Powershai + .DESCRIPTION - Envia uma mensagem para o modelo do provider configurado e escreve a resposta na tela! + Este cmdlet permite que você envie uma nova mensagem para o LLM do provider atual. + Por padrão, ele envia no chat ativo. Você pode sobrescrever o chat usando o parâmetro -Chat. Se não houver um chat ativo, ele irá usar o default. + + Diversos parâmetros do Chat afetam como este comando. Veja o comando Get-PowershaiChatParameter para mais info sobre os parâmetros do chat. + Além dos parâmetros do chat, os próprios parâmetros do comando podem sobrescrever comportamento. Para mais detalhes, consule a documentação de cada parâmetro deste cmdlet usando get-help. + + Para simplicidade, e manter a liha de comando limmpa, permitindo o usuário focar mais no prompt e nos dados, alguns alias são dosponibilizados. + Estes alias podem ativar certos parâmetros. + São eles: + ia + Abreviação de "Inteligência Artifical" em português. Este é um alias simples e não muda nenum parâmetro. Ele ajuda a reduzir bastante a linha de comando. + ai + Abreviação de "Artificial Inteligence", em inglês. Este também e um alias simples e muda nenhum parâmetro. Ajuda a reduzir bastante a linha de comando. + + iat,ait + O mesmo que Send-PowershaAIChat -Temporary + + io,ao + O mesmo que Send-PowershaAIChat -Object #> function Send-PowershaiChat { - [CmdletBinding()] + [CmdletBinding(PositionalBinding=$false)] param( - [parameter(mandatory=$false, position=1)] + [parameter(mandatory=$false, position=0, ValueFromRemainingArguments)] # o prompt a ser enviado ao modelo $prompt @@ -1866,14 +1948,40 @@ function Send-PowershaiChat { ,# Parametros da funcao de formatacao $FormatterParams = $null + + ,# Retorna as mensagens de volta no pipeline, sem escrever direto na tela! + # Esta opção assume que o usuário irá ser o responsável por dar o correto destino da mensagem! + # O objeto passado ao pipeline terá as seguintes propriedades: + # text - O texto (ou trecho) do texto retornado pelo modelo + # formatted - O texto formatado, incluindo o prompt, como se fosse escrito direto na tela (sem as cores) + # event - O evento. Indica o evento que originou. São os mesmos eventos documentaados em Invoke-AiChatTools + # interaction - O objeto interaction gerado por Invoke-AiChatTools + [switch]$PassThru ) begin { $ErrorActionPreference = "Stop"; - $MyInvok = $MyInvocation; - $CallName = $MyInvok.InvocationName; + $prompt = @($prompt) -Join " " + + $MyInvok = $MyInvocation; + $CallName = $MyInvok.InvocationName; + $MyRealName = $MyInvocation.MyCommand.Name; + $CurrentName = $CallName; + $MyParameters = GetMyParams + + # resolve até chegar no command! + $LoopProtection = 50; + while($CurrentName -ne $MyRealName -and $LoopProtection--){ + $CallName = $CurrentName; + $CurrentName = (Get-Alias $CurrentName).Definition + } + + if($LoopProtection -lt 0){ + write-warning "Cannot determined alias. This can be a bug!"; + } + if($CallName -eq "io"){ write-verbose "Invoked vias io alias. Setting Object to true!"; @@ -1898,6 +2006,44 @@ function Send-PowershaiChat { $AllContext = @() $IsPipeline = $PSCmdlet.MyInvocation.ExpectingInput + $MainCmdlet = $PsCmdLet; + + $ChatMyParams = Get-PowershaiChatParameter -ChatId $ActiveChat.id + $CurrentChatParams = @{ + all = @{} + direct = @{} + }; + + $ChatMyParams | %{ + if($_.direct){ + verbose "Adding direct param $($_.name)"; + $CurrentChatParams.direct[$_.name] = $_.value; + } + + $CurrentChatParams.all[$_.name] = $_.value; + } + + $ContextFormat = $CurrentChatParams.all.ContextFormat; + + if(!$ContextFormat){ + $ContextFormat = { + param($Params) + + $ContextObject = $Params.FormattedObject; + $UserPrompt = $Params.CmdParams.bound.prompt; + + @( + "Responda a mensagem com base nas informacoes de contexto que estao na tag " + "" + $ContextObject + "" + "Mensagem:" + $UserPrompt + ) + } + } + + function ProcessPrompt { param($prompt) @@ -1921,6 +2067,7 @@ function Send-PowershaiChat { function FormatPrompt { + $str = PowerShaiProviderFunc "FormatPrompt" -Ignore -FuncParams @{ model = $model } @@ -1958,10 +2105,24 @@ function Send-PowershaiChat { $prempt = FormatPrompt; $str = "`$($prempt)$text`n`n" } + + if($PassThru){ + $MessageOutput = @{ + event = $EventName + text = $text + formatted = $str + interaction = $interaction + } + + $MainCmdlet.WriteObject($MessageOutput); + return; + } if($str){ write-host @WriteParams $Str; } + + } #Get active chat! @@ -1975,21 +2136,12 @@ function Send-PowershaiChat { $ChatStats = $Chat.stats; $ChatMetadata = $Chat.metadata; $UserFirstName = $Chat.UserInfo.AllNames[0]; - $AllParams = Get-PowershaiChatParameter -ChatId $ActiveChat.id $ChatContext = $Chat.context; write-verbose "Iniciando..." - $ChatUserParams = @{} - $DirectParams = @{} - $AllParams | %{ - if($_.direct){ - verbose "Adding direct param $($_.name)"; - $DirectParams[$_.name] = $_.value; - } - - $ChatUserParams[$_.name] = $_.value; - } + $ChatUserParams = $CurrentChatParams.all + $DirectParams = $CurrentChatParams.Direct; $VerboseEnabled = $ChatUserParams.VerboseEnabled; $ShowFullSend = $ChatUserParams.ShowFullSend @@ -2124,6 +2276,11 @@ function Send-PowershaiChat { $funcName = $interaction.toolResults[-1].name + if($PassThru){ + $MainCmdlet.WriteObject(@{event="func";interaction=$interaction}); + return; + } + write-host -ForegroundColor Blue "$funcName{" -NoNewLine if($Chat.params.ShowArgs){ @@ -2142,6 +2299,11 @@ function Send-PowershaiChat { $LastResult = $funcName = $interaction.toolResults[-1].resp.content; + if($PassThru){ + $MainCmdlet.WriteObject(@{event="funcresult";interaction=$interaction}); + return; + } + if($Chat.params.PrintToolsResults){ write-host "Result:" write-host $LastResult @@ -2152,6 +2314,11 @@ function Send-PowershaiChat { exec = { param($interaction) + if($PassThru){ + $MainCmdlet.WriteObject(@{event="exec";interaction=$interaction}); + return; + } + write-host -ForegroundColor Blue "}" write-host "" } @@ -2208,9 +2375,9 @@ function Send-PowershaiChat { $Chat.history += $HistoryEntry if($Object){ - write-verbose "Object mode! Parsing json..." + verbose "Object mode! Parsing json..." $JsonContent = @($Ret.answer)[0].choices[0].message.content - write-verbose "Converting json to object: $JsonContent"; + verbose "Converting json to object: $JsonContent"; $JsonObject = $JsonContent | ConvertFrom-Json; $ResultObj = $JsonObject; @@ -2295,22 +2462,24 @@ function Send-PowershaiChat { function ProcessContext { param($context) - IF($PrintContext){ + if($PrintContext){ write-host -ForegroundColor Blue Contexto: write-host -ForegroundColor Blue $Context; } - write-verbose "Adding to context: $($Context|out-string)" - $FinalPrompt = @( - "Responda a mensagem com base nas informacoes de contexto que estao na tag " - "" - $context - "" - "Mensagem:" - $prompt - ) - ProcessPrompt $FinalPrompt + + $ContextScriptParams = @{ + FormattedObject = $Context + CmdParams = $MyParameters + Chat = $ActiveChat + } + + + $ContextPrompt = & $ContextFormat $ContextScriptParams + + write-verbose "Adding to context: $($Context|out-string)" + ProcessPrompt $ContextPrompt } @@ -2903,10 +3072,15 @@ function Get-AiUserToolbox { Set-Alias -Name PowerShai -Value Start-PowershaiChat + Set-Alias -Name ia -Value Send-PowershaiChat Set-Alias -Name iat -Value Send-PowershaiChat Set-Alias -Name io -Value Send-PowershaiChat -Set-Alias -Name iaf -Value Update-PowershaiChatFunctions + +Set-Alias -Name ai -Value ia +Set-Alias -Name ait -Value iat +Set-Alias -Name ao -Value io + <# function prompt {