param( [int]$StartupTimeoutSeconds = 180, [ValidateSet('auto','fast','balanced','lowvram')] [string]$Profile = 'auto' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path if ((Split-Path -Leaf $ScriptDir) -eq 'local-ai') { $VendooDir = Split-Path -Parent $ScriptDir } else { $VendooDir = Split-Path -Parent $ScriptDir } $LocalAiDir = Join-Path $VendooDir 'local-ai' $PortableRoot = Join-Path $LocalAiDir 'comfyui\ComfyUI_windows_portable' $Python = Join-Path $PortableRoot 'python_embeded\python.exe' $Main = Join-Path $PortableRoot 'ComfyUI\main.py' $LogDir = Join-Path $VendooDir 'logs' $StdoutLog = Join-Path $LogDir 'local-flux-runtime.log' $StderrLog = Join-Path $LogDir 'local-flux-runtime-error.log' $StateFile = Join-Path $LogDir 'local-flux-runtime-state.json' $PidFile = Join-Path $LogDir 'local-flux-runtime.pid' $ComfyUrl = 'http://127.0.0.1:8188' $ComfyPort = 8188 New-Item -ItemType Directory -Force -Path $LogDir | Out-Null $env:PYTHONUTF8 = '1' $env:PYTHONIOENCODING = 'utf-8' $env:NO_COLOR = '1' function Write-Utf8NoBom([string]$Path, [string]$Content) { $encoding = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($Path, $Content, $encoding) } function Remove-Ansi([string]$Text) { if ([string]::IsNullOrEmpty($Text)) { return '' } $pattern = ([string][char]27) + '\\[[0-?]*[ -/]*[@-~]' return [regex]::Replace($Text, $pattern, '') } function Read-Tail([string]$Path, [int]$Count = 16) { if (-not (Test-Path -LiteralPath $Path)) { return @() } try { return @(Get-Content -LiteralPath $Path -Tail $Count -ErrorAction SilentlyContinue | ForEach-Object { Remove-Ansi ([string]$_) }) } catch { return @() } } function Save-RuntimeState([string]$Status, [string]$Message, [int]$ProcessId = 0, [int]$Elapsed = 0, [string]$ErrorMessage = '') { $state = [ordered]@{ status = $Status message = $Message pid = if ($ProcessId -gt 0) { $ProcessId } else { $null } port = $ComfyPort url = $ComfyUrl elapsed_seconds = $Elapsed updated_at = (Get-Date).ToString('o') stdout_log = $StdoutLog stderr_log = $StderrLog error = $ErrorMessage stdout_tail = @(Read-Tail $StdoutLog 12) stderr_tail = @(Read-Tail $StderrLog 16) } Write-Utf8NoBom -Path $StateFile -Content ($state | ConvertTo-Json -Depth 5) if ($ProcessId -gt 0) { Write-Utf8NoBom -Path $PidFile -Content ([string]$ProcessId) } } function Test-ComfyFlag([string]$Flag) { try { $help = & $Python -s $Main --help 2>&1 | Out-String return $help -match [regex]::Escape($Flag) } catch { return $false } } function Get-NvidiaVramGb { try { $line = & nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>$null | Select-Object -First 1 if ($line -match '^\s*(\d+)') { return [math]::Round(([double]$Matches[1] / 1024), 1) } } catch {} return 0 } function Get-PerformanceArguments([string]$RequestedProfile) { $effective = $RequestedProfile $vramGb = Get-NvidiaVramGb if ($effective -eq 'auto') { if ($vramGb -ge 12) { $effective = 'fast' } elseif ($vramGb -gt 0 -and $vramGb -lt 8) { $effective = 'lowvram' } else { $effective = 'balanced' } } $flags = New-Object System.Collections.Generic.List[string] if ($effective -eq 'fast') { $flags.Add('--highvram') $flags.Add('--cache-classic') if (Test-ComfyFlag '--fast') { $flags.Add('--fast'); $flags.Add('fp16_accumulation'); $flags.Add('cublas_ops') } } elseif ($effective -eq 'lowvram') { $flags.Add('--lowvram') $flags.Add('--cache-none') } else { $flags.Add('--cache-classic') } return [pscustomobject]@{ profile = $effective; requested = $RequestedProfile; vram_gb = $vramGb; flags = @($flags) } } function Test-ComfyReady { try { $response = Invoke-WebRequest -UseBasicParsing -Uri "$ComfyUrl/system_stats" -TimeoutSec 4 return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300 } catch { return $false } } function Get-ComfyProcess { try { $items = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -and $_.CommandLine -match 'ComfyUI[\\/]main\.py' -and $_.CommandLine -match "--port\s+$ComfyPort" } | Select-Object -First 1) if ($items.Count -gt 0) { return $items[0] } return $null } catch { return $null } } try { if (-not (Test-Path -LiteralPath $Python)) { throw "Embedded Python wurde nicht gefunden: $Python" } if (-not (Test-Path -LiteralPath $Main)) { throw "ComfyUI main.py wurde nicht gefunden: $Main" } if (Test-ComfyReady) { $existingReady = Get-ComfyProcess $readyPid = if ($existingReady) { [int]$existingReady.ProcessId } else { 0 } Save-RuntimeState 'ready' 'FLUX / ComfyUI ist bereits aktiv und erreichbar.' $readyPid 0 exit 0 } $existing = Get-ComfyProcess $comfyPid = 0 $process = $null if ($existing) { $comfyPid = [int]$existing.ProcessId Save-RuntimeState 'starting' 'Ein vorhandener ComfyUI-Prozess initialisiert noch.' $comfyPid 0 } else { Remove-Item -LiteralPath $StdoutLog,$StderrLog -Force -ErrorAction SilentlyContinue $performance = Get-PerformanceArguments $Profile $arguments = @( '-s', $Main, '--listen', '127.0.0.1', '--port', [string]$ComfyPort, '--disable-auto-launch', '--preview-method', 'none', '--windows-standalone-build', '--disable-all-custom-nodes', '--log-stdout', '--verbose', 'INFO' ) foreach ($flag in $performance.flags) { $arguments += $flag } $process = Start-Process -FilePath $Python -ArgumentList $arguments -WorkingDirectory $PortableRoot -WindowStyle Hidden -RedirectStandardOutput $StdoutLog -RedirectStandardError $StderrLog -PassThru if (-not $process) { throw 'Der ComfyUI-Prozess konnte nicht gestartet werden.' } $comfyPid = [int]$process.Id Save-RuntimeState 'starting' ("FLUX / ComfyUI wird initialisiert · Profil: {0} · VRAM: {1} GB." -f $performance.profile, $performance.vram_gb) $comfyPid 0 } $startedAt = Get-Date while (((Get-Date) - $startedAt).TotalSeconds -lt $StartupTimeoutSeconds) { Start-Sleep -Seconds 2 $elapsed = [int][math]::Round(((Get-Date) - $startedAt).TotalSeconds) if (Test-ComfyReady) { Save-RuntimeState 'ready' "FLUX / ComfyUI ist nach ${elapsed}s erreichbar." $comfyPid $elapsed exit 0 } $alive = $false try { $alive = $null -ne (Get-Process -Id $comfyPid -ErrorAction SilentlyContinue) } catch {} if (-not $alive) { $stderrTail = (Read-Tail $StderrLog 24) -join "`n" $stdoutTail = (Read-Tail $StdoutLog 24) -join "`n" $detail = if ($stderrTail) { $stderrTail } elseif ($stdoutTail) { $stdoutTail } else { 'Der Prozess wurde ohne verwertbare Ausgabe beendet.' } Save-RuntimeState 'failed' 'ComfyUI wurde während des Starts beendet.' $comfyPid $elapsed $detail throw "ComfyUI wurde während des Starts beendet. $detail" } $tail = @((Read-Tail $StdoutLog 3) + (Read-Tail $StderrLog 3)) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $lastLine = if ($tail.Count -gt 0) { [string]$tail[-1] } else { 'ComfyUI initialisiert Python, Datenbank und Modelle.' } Save-RuntimeState 'starting' "Start läuft seit ${elapsed}s: $lastLine" $comfyPid $elapsed } $timeoutDetail = @((Read-Tail $StderrLog 24) + (Read-Tail $StdoutLog 24)) -join "`n" Stop-Process -Id $comfyPid -Force -ErrorAction SilentlyContinue Save-RuntimeState 'failed' "ComfyUI war nach ${StartupTimeoutSeconds}s noch nicht erreichbar und wurde beendet." $comfyPid $StartupTimeoutSeconds $timeoutDetail throw "ComfyUI war nach ${StartupTimeoutSeconds}s noch nicht erreichbar und wurde beendet. Prüfe $StderrLog und $StdoutLog." } catch { $message = Remove-Ansi ([string]$_.Exception.Message) $currentPid = 0 try { if (Test-Path -LiteralPath $PidFile) { $currentPid = [int](Get-Content -LiteralPath $PidFile -Raw).Trim() } } catch {} $elapsed = 0 try { if (Test-Path -LiteralPath $StateFile) { $oldState = Get-Content -LiteralPath $StateFile -Raw | ConvertFrom-Json $elapsed = [int]$oldState.elapsed_seconds } } catch {} Save-RuntimeState 'failed' 'FLUX / ComfyUI konnte nicht gestartet werden.' $currentPid $elapsed $message Write-Error $message exit 1 }