Files
aiagent/scripts/startup/manage.ps1
renjianbo f2e65a8fbb feat: virtual company module, team projects, PWA dishes app, and startup scripts overhaul
- Add company module (3-tier org, CEO planning, parallel departments)
- Add company orchestrator, knowledge extractor, presets, scheduler
- Add company API endpoints, models, and frontend views
- Add 今天吃啥 PWA app (69 dishes, real images, offline support)
- Add team_projects output directory structure
- Add unified manage.ps1 for service lifecycle
- Add Windows startup guide v1.0
- Add TTS troubleshooting doc
- Update frontend (AgentChat UX overhaul, new views)
- Update backend (voice engine fix, multi-tenant, RBAC)
- Remove deprecated startup scripts and old docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-10 22:37:56 +08:00

355 lines
12 KiB
PowerShell

<#
.SYNOPSIS
Tiangang AI Agent Platform - Unified Management Script v2.0
.DESCRIPTION
status - Check all services
start - Start all (Redis -> API -> Celery -> Frontend)
stop - Stop all
restart - Stop all, then start all
.EXAMPLE
powershell -ExecutionPolicy Bypass -File .\scripts\startup\manage.ps1 status
powershell -ExecutionPolicy Bypass -File .\scripts\startup\manage.ps1 restart
#>
param(
[ValidateSet("status", "start", "stop", "restart")]
[string]$Action = "status"
)
$ErrorActionPreference = "Continue"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = Split-Path -Parent (Split-Path -Parent $ScriptDir)
$Backend = Join-Path $RepoRoot "backend"
$Frontend = Join-Path $RepoRoot "frontend"
$RedisExe = Join-Path $Backend "redis\redis-server.exe"
$RedisPort = 6379
$PrimaryApiPort = 8037
$FallbackApiPort = 8041
$FrontendPort = 3001
$script:ApiPort = $PrimaryApiPort
# ========== UTILITY FUNCTIONS ==========
function Write-Step { param([string]$Msg) Write-Host ">>> $Msg" -ForegroundColor Cyan }
function Write-OK { param([string]$Msg) Write-Host " [OK] $Msg" -ForegroundColor Green }
function Write-Fail { param([string]$Msg) Write-Host " [FAIL] $Msg" -ForegroundColor Red }
function Write-Warn { param([string]$Msg) Write-Host " [WARN] $Msg" -ForegroundColor Yellow }
function Write-Info { param([string]$Msg) Write-Host " [INFO] $Msg" -ForegroundColor Gray }
function Test-PortListening([int]$Port) {
$conn = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
return ($conn -ne $null)
}
function Stop-ByPattern([string]$Pattern, [string]$Label) {
$procs = Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match $Pattern
}
if (-not $procs) {
Write-Info "$Label - not running"
return
}
foreach ($p in $procs) {
try {
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
Write-OK "Stopped $Label (PID=$($p.ProcessId))"
} catch {
Write-Fail "Failed to stop $Label (PID=$($p.ProcessId)): $_"
}
}
}
function Start-Background([string]$WorkDir, [string]$Command, [string]$Label) {
$fullCmd = "Set-Location '$WorkDir'; .\venv\Scripts\Activate.ps1 2>`$null; $Command"
$proc = Start-Process powershell -ArgumentList @(
"-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden",
"-Command", $fullCmd
) -PassThru
Write-OK "$Label started (PID=$($proc.Id))"
return $proc
}
function Start-Visible([string]$WorkDir, [string]$Command, [string]$Label) {
$fullCmd = "Set-Location '$WorkDir'; .\venv\Scripts\Activate.ps1 2>`$null; $Command; Write-Host ''; Write-Host '>>> $Label exited. Press any key to close...'; `$null = `$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')"
$proc = Start-Process powershell -ArgumentList @(
"-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass",
"-Command", $fullCmd
) -PassThru
Write-OK "$Label started in new window (PID=$($proc.Id))"
return $proc
}
function Start-Frontend([string]$FeDir, [int]$FePort, [int]$ApiPort, [string]$Label) {
# Resolve npx full path first (hidden PowerShell windows lack user PATH)
$npxPath = (Get-Command npx.cmd -ErrorAction SilentlyContinue).Source
if (-not $npxPath) { $npxPath = (Get-Command npx -ErrorAction SilentlyContinue).Source }
if (-not $npxPath) { $npxPath = "npx" }
# Use cmd /k (stay open) to run the whole command chain in a single visible window.
# Avoids "cmd /c start" which breaks && chains: start only receives the first
# command after the title, and the rest runs in the outer cmd (wrong cwd).
$feCmd = "cd /d `"$FeDir`" && set AIAGENT_API_PROXY=http://127.0.0.1:$ApiPort && `"$npxPath`" vite --port $FePort --host 0.0.0.0"
$proc = Start-Process cmd -ArgumentList @(
"/k", $feCmd
) -PassThru
Write-OK "$Label started (PID=$($proc.Id), npx=$npxPath)"
return $proc
}
function Test-HealthEndpoint([int]$Port) {
try {
$result = curl.exe -s -o NUL -w "%{http_code}" --connect-timeout 5 "http://127.0.0.1:${Port}/docs" 2>$null
return ($result -eq "200")
} catch {
return $false
}
}
# ========== STATUS ==========
function Invoke-Status {
Write-Host ""
Write-Host "=============================================" -ForegroundColor Cyan
Write-Host " Service Status" -ForegroundColor Cyan
Write-Host "=============================================" -ForegroundColor Cyan
Write-Host ""
# Redis
if (Test-PortListening $RedisPort) {
Write-OK "Redis port $RedisPort - running"
} else {
Write-Fail "Redis port $RedisPort - NOT running"
}
# API
$apiPort = 0
if (Test-PortListening $PrimaryApiPort) {
$apiPort = $PrimaryApiPort
} elseif (Test-PortListening $FallbackApiPort) {
$apiPort = $FallbackApiPort
}
if ($apiPort -gt 0) {
$healthy = Test-HealthEndpoint $apiPort
$mark = if ($healthy) { "OK" } else { "no response" }
Write-OK "Backend API port $apiPort - running [/docs $mark]"
} else {
Write-Fail "Backend API ports $PrimaryApiPort/$FallbackApiPort - NOT running"
}
# Celery Worker
$workers = Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match "celery.*worker"
}
if ($workers) {
Write-OK "Celery Worker $($workers.Count) process(es)"
} else {
Write-Fail "Celery Worker NOT running"
}
# Celery Beat
$beats = Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match "celery.*beat"
}
if ($beats) {
Write-OK "Celery Beat running"
} else {
Write-Warn "Celery Beat NOT running (scheduled tasks will not trigger)"
}
# Frontend
if (Test-PortListening $FrontendPort) {
Write-OK "Frontend Vite port $FrontendPort - running"
} else {
Write-Fail "Frontend Vite port $FrontendPort - NOT running"
}
Write-Host ""
Write-Host "URLs:" -ForegroundColor White
if ($apiPort -gt 0) {
Write-Host " API docs: http://127.0.0.1:${apiPort}/docs" -ForegroundColor Gray
}
Write-Host " Frontend: http://localhost:${FrontendPort}" -ForegroundColor Gray
Write-Host " AgentChat: http://localhost:${FrontendPort}/agent-chat" -ForegroundColor Gray
Write-Host ""
}
# ========== STOP ==========
function Invoke-Stop {
Write-Host ""
Write-Host "== Stop All Services ==" -ForegroundColor Cyan
Stop-ByPattern "uvicorn\s+app\.main:app" "Backend API"
Stop-ByPattern "celery\s+-A\s+app\.core\.celery_app\s+worker" "Celery Worker"
Stop-ByPattern "celery\s+-A\s+app\.core\.celery_app\s+beat" "Celery Beat"
Stop-ByPattern "vite.*3001|vite.*--port" "Frontend Vite"
# Redis
$redisProcs = Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match "redis-server"
}
if ($redisProcs) {
foreach ($p in $redisProcs) {
try {
Stop-Process -Id $p.ProcessId -Force
Write-OK "Stopped Redis (PID=$($p.ProcessId))"
} catch {
Write-Fail "Failed to stop Redis (PID=$($p.ProcessId))"
}
}
} else {
Write-Info "Redis - not running"
}
Start-Sleep -Seconds 2
Write-Host ""
Write-Host "Port status:" -ForegroundColor Cyan
foreach ($port in @($RedisPort, $PrimaryApiPort, $FallbackApiPort, $FrontendPort)) {
$busy = Test-PortListening $port
$label = if ($busy) { "BUSY" } else { "free" }
$color = if ($busy) { "Yellow" } else { "Green" }
Write-Host " port $port : $label" -ForegroundColor $color
}
Write-Host ""
Write-Host "DONE: All services stopped" -ForegroundColor Green
}
# ========== START ==========
function Invoke-Start {
Write-Host ""
Write-Host "== Start All Services ==" -ForegroundColor Cyan
# 1. Redis
Write-Step "1/5 Redis"
if (Test-PortListening $RedisPort) {
Write-OK "Redis already running"
} else {
if (Test-Path $RedisExe) {
Start-Process $RedisExe -ArgumentList "--port", $RedisPort -WindowStyle Hidden
Start-Sleep -Seconds 1
if (Test-PortListening $RedisPort) {
Write-OK "Redis started (port $RedisPort)"
} else {
Write-Fail "Redis failed to start"
}
} else {
Write-Fail "redis-server.exe not found: $RedisExe"
}
}
# 2. API port detection
Write-Step "2/5 Backend API port check"
if (Test-PortListening $PrimaryApiPort) {
Write-Warn "Port $PrimaryApiPort is busy, using fallback $FallbackApiPort"
$script:ApiPort = $FallbackApiPort
} else {
$script:ApiPort = $PrimaryApiPort
Write-OK "Port $($script:ApiPort) available"
}
# 3. API (uvicorn)
Write-Step "3/5 Backend API (port $($script:ApiPort))"
$existingApi = @(Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match "uvicorn\s+app\.main:app"
})
if ($existingApi.Count -gt 0) {
Write-OK "Backend API already running"
} else {
$null = Start-Background $Backend "python -m uvicorn app.main:app --host 0.0.0.0 --port $($script:ApiPort)" "Backend API"
}
# 4. Celery
Write-Step "4/5 Celery Worker + Beat"
$existingWorker = @(Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match "celery.*worker"
})
if ($existingWorker.Count -gt 0) {
Write-OK "Celery Worker already running"
} else {
$null = Start-Visible $Backend "python -m celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8" "Celery Worker"
}
$existingBeat = @(Get-CimInstance Win32_Process | Where-Object {
$_.CommandLine -and $_.CommandLine -match "celery.*beat"
})
if ($existingBeat.Count -gt 0) {
Write-OK "Celery Beat already running"
} else {
$null = Start-Visible $Backend "python -m celery -A app.core.celery_app beat --loglevel=info" "Celery Beat"
}
# 5. Frontend
Write-Step "5/5 Frontend Vite (port $FrontendPort)"
if (Test-PortListening $FrontendPort) {
Write-OK "Frontend already running"
} else {
$null = Start-Frontend $Frontend $FrontendPort $script:ApiPort "Frontend-Vite"
}
# Health check
Write-Host ""
Write-Step "Health check (waiting up to 30s for services)"
$maxWait = 30
$apiReady = $false
$feReady = $false
for ($i = 1; $i -le $maxWait; $i++) {
if (-not $apiReady) {
$apiReady = Test-HealthEndpoint $script:ApiPort
}
if (-not $feReady) {
$feReady = Test-PortListening $FrontendPort
}
if ($apiReady -and $feReady) {
Write-OK "All services ready (${i}s)"
break
}
if ($i % 5 -eq 0) {
Write-Info "Waiting... (${i}s) API=$apiReady Frontend=$feReady"
}
Start-Sleep -Seconds 1
}
if (-not $apiReady) {
Write-Fail "Backend API not ready within ${maxWait}s, check logs"
}
if (-not $feReady) {
Write-Fail "Frontend not ready within ${maxWait}s, check logs"
}
# Summary
Write-Host ""
Write-Host "=============================================" -ForegroundColor Green
Write-Host " Startup Complete" -ForegroundColor Green
Write-Host "=============================================" -ForegroundColor Green
Write-Host " API docs: http://127.0.0.1:$($script:ApiPort)/docs" -ForegroundColor White
Write-Host " Frontend: http://localhost:${FrontendPort}" -ForegroundColor White
Write-Host " AgentChat: http://localhost:${FrontendPort}/agent-chat" -ForegroundColor White
Write-Host ""
}
# ========== RESTART ==========
function Invoke-Restart {
Invoke-Stop
Start-Sleep -Seconds 3
Invoke-Start
}
# ========== ENTRY ==========
switch ($Action) {
"status" { Invoke-Status }
"start" { Invoke-Start }
"stop" { Invoke-Stop }
"restart" { Invoke-Restart }
}