- 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>
355 lines
12 KiB
PowerShell
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 }
|
|
}
|