param( [string]$InstallRoot = (Join-Path $env:LOCALAPPDATA "MINIKE-Local-AI"), [ValidateSet("auto", "gpu", "low_vram", "cpu")] [string]$Profile = "auto", [string]$BaseUrl = "https://minike.com.br/local-ai", [string]$ComfyUIBaseUrl = "http://127.0.0.1:8188", [switch]$StartComfyUI, [switch]$SkipCheckpointDownload, [switch]$ValidateOnly ) $ErrorActionPreference = "Stop" function Write-Step { param([int]$Percent, [string]$Message) Write-Host ("[{0,3}%] {1}" -f $Percent, $Message) } function Test-LoopbackUrl { param([string]$Url) try { $uri = [uri]$Url return ($uri.Scheme -eq "http" -and $uri.Host -in @("127.0.0.1", "localhost", "::1")) } catch { return $false } } function Get-CommandReady { param([string]$Name) return [bool](Get-Command $Name -ErrorAction SilentlyContinue) } function Get-GhostscriptCommandPath { $cmd = Get-Command "gswin64c.exe" -ErrorAction SilentlyContinue if ($cmd) { return [string]$cmd.Source } $cmd = Get-Command "gswin32c.exe" -ErrorAction SilentlyContinue if ($cmd) { return [string]$cmd.Source } $roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}) | Where-Object { $_ } foreach ($root in $roots) { foreach ($file in @(Get-ChildItem -LiteralPath (Join-Path $root "gs") -Recurse -Filter "gswin*c.exe" -File -ErrorAction SilentlyContinue | Sort-Object FullName -Descending)) { return [string]$file.FullName } } return "" } function Install-Ghostscript { if (Get-GhostscriptCommandPath) { return [pscustomobject]@{ name = "Ghostscript"; status = "installed"; action = "already_present" } } $arch = if ([Environment]::Is64BitOperatingSystem) { "w64" } else { "w32" } $fileName = if ($arch -eq "w64") { "gs10071w64.exe" } else { "gs10071w32.exe" } $url = "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10071/" + $fileName $installer = Join-Path $toolsRoot $fileName Write-Step 20 "Instalando Ghostscript oficial para PDF" Save-RemoteFile -Url $url -Path $installer $p = Start-Process -FilePath $installer -ArgumentList @("/S") -Wait -PassThru if ($p.ExitCode -ne 0) { return [pscustomobject]@{ name = "Ghostscript"; status = "guided"; action = "official_installer_failed"; exit_code = $p.ExitCode } } $path = Get-GhostscriptCommandPath return [pscustomobject]@{ name = "Ghostscript"; status = $(if ($path) { "installed" } else { "pending_restart_or_path" }); action = "official_download"; source = $path } } function Get-WorkingPythonPath { $candidates = @() # 1) Caminho absoluto em LocalAppData (instalacao real, mais confiavel) $candidates += Get-ChildItem "$env:LOCALAPPDATA\Programs\Python\Python3*\python.exe" -ErrorAction SilentlyContinue | ForEach-Object { [pscustomobject]@{ path=$_.FullName; prefix=@() } } # 2) python.exe do PATH - somente se nao for o atalho WindowsApps $command = Get-Command python.exe -ErrorAction SilentlyContinue if ($command -and $command.Source -notmatch "WindowsApps\\python") { $candidates += [pscustomobject]@{ path=$command.Source; prefix=@() } } # NAO usamos py.exe -3.11 aqui: o launcher WindowsApps abre a Microsoft Store # em vez de retornar exit code real, e o venv nunca e criado. foreach ($candidate in $candidates) { try { $version = & $candidate.path @($candidate.prefix) --version 2>&1 if ($LASTEXITCODE -eq 0 -and ($version -join " ") -match "Python 3\.(10|11|12)(\.|$)") { return [string]$candidate.path } } catch {} } return "" } function Install-WingetPackage { param([string]$Id, [string]$Command, [string]$Name) if (Get-CommandReady $Command) { return [pscustomobject]@{ name = $Name; status = "installed"; action = "already_present" } } if (-not (Get-CommandReady "winget")) { return [pscustomobject]@{ name = $Name; status = "guided"; action = "winget_missing" } } Write-Step 18 ("Instalando " + $Name + " via winget") & winget install --id $Id -e --accept-package-agreements --accept-source-agreements | Out-Null return [pscustomobject]@{ name = $Name; status = $(if (Get-CommandReady $Command) { "installed" } else { "pending_restart_or_path" }); action = "winget" } } function Save-RemoteFile { param([string]$Url, [string]$Path) $dir = Split-Path -Parent $Path if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 (New-Object Net.WebClient).DownloadFile($Url, $Path) } function Resolve-MultimediaProfile { if ($Profile -ne "auto") { return $Profile } $gpuNames = @() try { $gpuNames = @(Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue | ForEach-Object { [string]$_.Name }) } catch { $gpuNames = @() } if (@($gpuNames | Where-Object { $_ -match "NVIDIA|RTX|GTX" }).Count -gt 0) { return "gpu" } if (@($gpuNames | Where-Object { $_ -match "AMD|Radeon|Arc" }).Count -gt 0) { return "low_vram" } return "cpu" } function Update-MikeConfig { param([string]$Root, [string]$ComfyRoot, [string]$ResolvedProfile) $configPath = Join-Path $Root "config.json" $cfg = [pscustomobject]@{} if (Test-Path -LiteralPath $configPath) { try { $cfg = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json } catch { $cfg = [pscustomobject]@{} } } if (-not $cfg.PSObject.Properties.Name.Contains("comfyui")) { $cfg | Add-Member -NotePropertyName "comfyui" -NotePropertyValue ([pscustomobject]@{}) -Force } $cfg.comfyui | Add-Member -NotePropertyName "enabled" -NotePropertyValue $true -Force $cfg.comfyui | Add-Member -NotePropertyName "root" -NotePropertyValue $ComfyRoot -Force $cfg.comfyui | Add-Member -NotePropertyName "base_url" -NotePropertyValue ($ComfyUIBaseUrl.TrimEnd("/")) -Force $cfg.comfyui | Add-Member -NotePropertyName "profile" -NotePropertyValue $ResolvedProfile -Force $cfg.comfyui | Add-Member -NotePropertyName "checkpoint" -NotePropertyValue "" -Force $cfg.comfyui | Add-Member -NotePropertyName "note" -NotePropertyValue "Motor instalado por skill multimidia. Coloque um checkpoint Stable Diffusion ou SDXL em ComfyUI\models\checkpoints para gerar imagens." -Force $cfg | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $configPath -Encoding UTF8 } Write-Host "================================================================" Write-Host " MIKE IA LOCAL - SKILLS MULTIMIDIA" Write-Host "================================================================" Write-Host " Instala modulos pesados apenas em PCs bons." Write-Host " Imagem: ComfyUI. Audio/video: FFmpeg. OCR: Tesseract." Write-Host "================================================================" if (-not (Test-LoopbackUrl $ComfyUIBaseUrl)) { throw "ComfyUIBaseUrl deve ser HTTP local, ex: http://127.0.0.1:8188" } $InstallRoot = [IO.Path]::GetFullPath($InstallRoot) $BaseUrl = $BaseUrl.TrimEnd("/") $resolvedProfile = Resolve-MultimediaProfile $comfyRoot = Join-Path $InstallRoot "ComfyUI" $toolsRoot = Join-Path $InstallRoot "multimedia-skills" $reportPath = Join-Path $InstallRoot "multimedia-skills-report.json" Write-Step 5 ("Pasta Mike: " + $InstallRoot) Write-Step 8 ("Perfil escolhido: " + $resolvedProfile) if ($ValidateOnly) { Write-Step 100 "Validacao sintatica concluida. Nenhuma instalacao executada." return } New-Item -ItemType Directory -Path $InstallRoot, $toolsRoot -Force | Out-Null $results = @() Write-Step 12 "Preparando dependencias de midia" $results += Install-WingetPackage -Id "Git.Git" -Command "git" -Name "Git" $results += Install-WingetPackage -Id "Python.Python.3.12" -Command "python" -Name "Python 3.12" $results += Install-WingetPackage -Id "Gyan.FFmpeg" -Command "ffmpeg" -Name "FFmpeg" $results += Install-WingetPackage -Id "ImageMagick.ImageMagick" -Command "magick" -Name "ImageMagick" $results += Install-WingetPackage -Id "UB-Mannheim.TesseractOCR" -Command "tesseract" -Name "Tesseract OCR" $results += Install-Ghostscript $pythonPath = Get-WorkingPythonPath if (-not $pythonPath) { $winget = Get-Command winget.exe -ErrorAction SilentlyContinue if (-not $winget) { throw "Python 3.10-3.12 real nao encontrado e winget indisponivel. O alias da Microsoft Store nao e uma instalacao valida." } Write-Step 24 "Substituindo alias da Microsoft Store por Python 3.11 real" & $winget.Source install --id Python.Python.3.11 --exact --silent --accept-package-agreements --accept-source-agreements --disable-interactivity | Out-Host if ($LASTEXITCODE -ne 0) { throw "Falha ao instalar Python 3.11 real pelo winget." } # Winget adiciona o Python em PATH somente em sessoes novas. Forcar busca em LocalAppData # alem de tentar refresh via $env:Path. $userPython = Join-Path $env:LOCALAPPDATA "Programs\Python\Python311\python.exe" if (Test-Path -LiteralPath $userPython) { Write-Step 25 ("Python 3.11 detectado em " + $userPython) } else { $pyDirs = @("$env:LOCALAPPDATA\Programs\Python\Python311", "$env:LOCALAPPDATA\Programs\Python\Python311\Scripts") foreach ($d in $pyDirs) { if (Test-Path -LiteralPath $d) { if ($env:Path -notlike "*$d*") { $env:Path = "$d;$env:Path" } } } } $pythonPath = Get-WorkingPythonPath } if (-not $pythonPath) { throw "Python 3.11 foi solicitado, mas o executavel real nao foi localizado. A Mike nao marcara ComfyUI como pronto; reabra o terminal e tente novamente." } Write-Step 38 "Instalando rembg (remocao de fundo por IA local)" $rembgStatus = "skipped" try { & $pythonPath -m pip install --quiet --disable-pip-version-check "rembg[cpu]" 2>&1 | Out-Null $rembgStatus = if ($LASTEXITCODE -eq 0) { "installed" } else { "failed" } } catch { $rembgStatus = "failed" } Write-Step 42 "Baixando instaladores do motor de imagem (portatil: AMD/NVIDIA/CPU)" # Instalador PORTATIL: detecta a GPU e escolhe o backend do PyTorch sozinho # (NVIDIA->CUDA, AMD/Intel->DirectML, sem GPU->CPU). Resolve "funcionar nos dois mundos". $comfyInstaller = Join-Path $toolsRoot "install-comfyui-portable.ps1" $comfyAgent = Join-Path $toolsRoot "comfyui-mike-agent.ps1" $workflow = Join-Path $toolsRoot "comfyui-workflows\text-to-image-basic.json" Save-RemoteFile -Url ($BaseUrl + "/install-comfyui-portable.ps1") -Path $comfyInstaller Save-RemoteFile -Url ($BaseUrl + "/comfyui-mike-agent.ps1") -Path $comfyAgent Save-RemoteFile -Url ($BaseUrl + "/comfyui-workflows/text-to-image-basic.json") -Path $workflow Write-Step 58 "Instalando ComfyUI local (backend automatico por GPU)" # -Backend auto / -Profile auto: o portatil analisa o PC (GPU, RAM, pagefile) e baixa o # stack certo, deixando pronto para uso. -Checkpoint sd15 cabe em PCs fracos. $comfyArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $comfyInstaller, "-InstallRoot", $InstallRoot, "-Backend", "auto", "-Profile", "auto", "-ComfyUIBaseUrl", $ComfyUIBaseUrl, "-Checkpoint", "sd15") $comfyArgs += @("-PythonPath", $pythonPath) & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" @comfyArgs if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath (Join-Path $comfyRoot ".venv\Scripts\python.exe") -PathType Leaf)) { throw "A instalacao do ComfyUI falhou. A configuracao da Mike nao foi alterada." } if (-not $SkipCheckpointDownload) { Write-Step 68 "Baixando checkpoint inicial para gerar imagens" $heavyInstaller = Join-Path $toolsRoot "install-mike-heavy-models.ps1" $modelManifest = Join-Path $toolsRoot "mike-secondary-models.json" Save-RemoteFile -Url ($BaseUrl + "/install-mike-heavy-models.ps1") -Path $heavyInstaller Save-RemoteFile -Url ($BaseUrl + "/mike-secondary-models.json") -Path $modelManifest & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File $heavyInstaller -InstallRoot $InstallRoot -Profile creator -ManifestPath $modelManifest if ($LASTEXITCODE -ne 0) { throw "Falha ao baixar o checkpoint inicial do ComfyUI." } } if ($StartComfyUI) { $comfyPython = Join-Path $comfyRoot ".venv\Scripts\python.exe" $startArgs = @("main.py", "--listen", "127.0.0.1", "--port", "8188") if ($resolvedProfile -eq "cpu") { $startArgs += "--cpu" } elseif ($resolvedProfile -eq "low_vram") { $startArgs += "--lowvram" } Start-Process -FilePath $comfyPython -ArgumentList $startArgs -WorkingDirectory $comfyRoot -WindowStyle Hidden | Out-Null } Write-Step 76 "Ligando ComfyUI na configuracao da Mike" Copy-Item -LiteralPath $comfyAgent -Destination (Join-Path $InstallRoot "comfyui-mike-agent.ps1") -Force New-Item -ItemType Directory -Path (Join-Path $InstallRoot "comfyui-workflows") -Force | Out-Null Copy-Item -LiteralPath $workflow -Destination (Join-Path $InstallRoot "comfyui-workflows\text-to-image-basic.json") -Force Update-MikeConfig -Root $InstallRoot -ComfyRoot $comfyRoot -ResolvedProfile $resolvedProfile $checkpointDir = Join-Path $comfyRoot "models\checkpoints" New-Item -ItemType Directory -Path $checkpointDir -Force | Out-Null $report = [ordered]@{ ok = $true installed_at = (Get-Date).ToString("o") install_root = $InstallRoot profile = $resolvedProfile comfyui = [ordered]@{ root = $comfyRoot base_url = $ComfyUIBaseUrl.TrimEnd("/") started = [bool]$StartComfyUI checkpoint_dir = $checkpointDir checkpoint_required = $true } audio_video = [ordered]@{ ffmpeg = [bool](Get-CommandReady "ffmpeg") transcription_runtime = "guided_whisper_cpp_or_cloud_byok" } ocr = [ordered]@{ tesseract = [bool](Get-CommandReady "tesseract") } image_background_removal = [ordered]@{ rembg = $rembgStatus } dependencies = $results next_steps = @( "Reabra a Mike IA Local para recarregar capacidades.", "Para gerar imagem 100% local, coloque um modelo Stable Diffusion ou SDXL em: $checkpointDir", "Audio/video ficam preparados com FFmpeg; transcricao completa ainda depende de whisper.cpp/modelo ou provedor autorizado." ) } $report | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $reportPath -Encoding UTF8 Write-Step 92 "Relatorio salvo em $reportPath" Write-Step 100 "Skills multimidia instaladas. Reabra a Mike e veja Capacidades."