Spaces:
Sleeping
Sleeping
Filipp Trigub commited on
Commit ·
6f5bda8
1
Parent(s): c1cbe7e
add testing and logging
Browse files- backend/fastapi_server.py +169 -25
- frontend/app/page.tsx +206 -67
backend/fastapi_server.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
#!/usr/bin/env python
|
| 2 |
"""
|
| 3 |
-
FastAPI server for AI Battle Royale Mental Manipulation
|
| 4 |
This server provides streaming endpoints for agent interactions and tool executions
|
|
|
|
| 5 |
"""
|
| 6 |
import os
|
| 7 |
import json
|
|
@@ -9,13 +10,17 @@ import random
|
|
| 9 |
import logging
|
| 10 |
import asyncio
|
| 11 |
import openai
|
|
|
|
|
|
|
| 12 |
from typing import Dict, List, Any, Optional, Union
|
| 13 |
from abc import ABC, abstractmethod
|
| 14 |
-
from fastapi import FastAPI, HTTPException
|
| 15 |
-
from fastapi.responses import StreamingResponse, FileResponse
|
| 16 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 17 |
from pydantic import BaseModel
|
| 18 |
from dotenv import load_dotenv
|
|
|
|
| 19 |
|
| 20 |
# Load environment variables for API keys
|
| 21 |
load_dotenv()
|
|
@@ -68,23 +73,82 @@ MODEL_PROVIDERS = {
|
|
| 68 |
}
|
| 69 |
}
|
| 70 |
|
| 71 |
-
# Configure logging
|
| 72 |
-
logging.basicConfig(
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
-
# FastAPI app instance
|
| 77 |
-
app = FastAPI(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
# Add CORS middleware
|
| 80 |
app.add_middleware(
|
| 81 |
CORSMiddleware,
|
| 82 |
-
allow_origins=["*"],
|
| 83 |
allow_credentials=True,
|
| 84 |
-
allow_methods=["
|
| 85 |
-
allow_headers=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
)
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
class SimpleGameState:
|
| 90 |
"""Simplified game state for testing mental manipulation mechanics"""
|
|
@@ -1092,26 +1156,106 @@ async def delete_game(game_id: str):
|
|
| 1092 |
|
| 1093 |
|
| 1094 |
@app.get("/")
|
| 1095 |
-
async def root():
|
| 1096 |
-
"""Root endpoint - serves as health check"""
|
|
|
|
|
|
|
| 1097 |
return {
|
| 1098 |
-
"status": "
|
| 1099 |
"message": "AI Battle Royale server is running",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1100 |
"note": "API keys are provided by frontend, not environment variables"
|
| 1101 |
}
|
| 1102 |
|
| 1103 |
|
| 1104 |
@app.get("/health")
|
| 1105 |
-
async def health_check():
|
| 1106 |
-
"""
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1111 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1112 |
|
| 1113 |
|
| 1114 |
-
|
| 1115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1116 |
|
| 1117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
#!/usr/bin/env python
|
| 2 |
"""
|
| 3 |
+
Enhanced FastAPI server for AI Battle Royale Mental Manipulation
|
| 4 |
This server provides streaming endpoints for agent interactions and tool executions
|
| 5 |
+
With comprehensive debugging, health checks, and CORS handling for HF Spaces
|
| 6 |
"""
|
| 7 |
import os
|
| 8 |
import json
|
|
|
|
| 10 |
import logging
|
| 11 |
import asyncio
|
| 12 |
import openai
|
| 13 |
+
import time
|
| 14 |
+
import traceback
|
| 15 |
from typing import Dict, List, Any, Optional, Union
|
| 16 |
from abc import ABC, abstractmethod
|
| 17 |
+
from fastapi import FastAPI, HTTPException, Request, Response
|
| 18 |
+
from fastapi.responses import StreamingResponse, FileResponse, JSONResponse
|
| 19 |
from fastapi.middleware.cors import CORSMiddleware
|
| 20 |
+
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
| 21 |
from pydantic import BaseModel
|
| 22 |
from dotenv import load_dotenv
|
| 23 |
+
import uvicorn
|
| 24 |
|
| 25 |
# Load environment variables for API keys
|
| 26 |
load_dotenv()
|
|
|
|
| 73 |
}
|
| 74 |
}
|
| 75 |
|
| 76 |
+
# Configure enhanced logging for debugging
|
| 77 |
+
logging.basicConfig(
|
| 78 |
+
level=logging.DEBUG, # Changed to DEBUG for more detailed logs
|
| 79 |
+
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
| 80 |
+
handlers=[
|
| 81 |
+
logging.StreamHandler(),
|
| 82 |
+
logging.FileHandler('/home/node/logs/backend_app.log', mode='a')
|
| 83 |
+
]
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
logger = logging.getLogger(__name__)
|
| 87 |
|
| 88 |
+
# FastAPI app instance with enhanced configuration
|
| 89 |
+
app = FastAPI(
|
| 90 |
+
title="AI Battle Royale",
|
| 91 |
+
description="Mental Manipulation Battle System - Enhanced for HF Spaces",
|
| 92 |
+
version="1.0.0",
|
| 93 |
+
docs_url="/docs",
|
| 94 |
+
redoc_url="/redoc"
|
| 95 |
+
)
|
| 96 |
|
| 97 |
+
# Add comprehensive CORS middleware
|
| 98 |
app.add_middleware(
|
| 99 |
CORSMiddleware,
|
| 100 |
+
allow_origins=["*"], # Allow all origins for HF Spaces
|
| 101 |
allow_credentials=True,
|
| 102 |
+
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
|
| 103 |
+
allow_headers=[
|
| 104 |
+
"*",
|
| 105 |
+
"Authorization",
|
| 106 |
+
"Content-Type",
|
| 107 |
+
"X-Requested-With",
|
| 108 |
+
"Accept",
|
| 109 |
+
"Origin",
|
| 110 |
+
"Access-Control-Request-Method",
|
| 111 |
+
"Access-Control-Request-Headers",
|
| 112 |
+
],
|
| 113 |
+
expose_headers=["*"]
|
| 114 |
)
|
| 115 |
|
| 116 |
+
# Add middleware for request/response logging
|
| 117 |
+
@app.middleware("http")
|
| 118 |
+
async def log_requests(request: Request, call_next):
|
| 119 |
+
start_time = time.time()
|
| 120 |
+
|
| 121 |
+
# Log incoming request
|
| 122 |
+
logger.info(f"🔍 Incoming request: {request.method} {request.url}")
|
| 123 |
+
logger.debug(f" Headers: {dict(request.headers)}")
|
| 124 |
+
|
| 125 |
+
try:
|
| 126 |
+
response = await call_next(request)
|
| 127 |
+
process_time = time.time() - start_time
|
| 128 |
+
|
| 129 |
+
# Log response
|
| 130 |
+
logger.info(f"✅ Response: {response.status_code} in {process_time:.3f}s")
|
| 131 |
+
|
| 132 |
+
# Add debug headers to response
|
| 133 |
+
response.headers["X-Process-Time"] = str(process_time)
|
| 134 |
+
response.headers["X-Server-Status"] = "healthy"
|
| 135 |
+
|
| 136 |
+
return response
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
process_time = time.time() - start_time
|
| 140 |
+
logger.error(f"❌ Request failed: {str(e)} in {process_time:.3f}s")
|
| 141 |
+
logger.error(f" Traceback: {traceback.format_exc()}")
|
| 142 |
+
|
| 143 |
+
return JSONResponse(
|
| 144 |
+
status_code=500,
|
| 145 |
+
content={
|
| 146 |
+
"detail": f"Internal server error: {str(e)}",
|
| 147 |
+
"type": "internal_error",
|
| 148 |
+
"timestamp": time.time()
|
| 149 |
+
}
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
|
| 153 |
class SimpleGameState:
|
| 154 |
"""Simplified game state for testing mental manipulation mechanics"""
|
|
|
|
| 1156 |
|
| 1157 |
|
| 1158 |
@app.get("/")
|
| 1159 |
+
async def root(request: Request):
|
| 1160 |
+
"""Root endpoint - serves as comprehensive health check"""
|
| 1161 |
+
logger.info(f"🏠 Root endpoint accessed from {request.client.host if request.client else 'unknown'}")
|
| 1162 |
+
|
| 1163 |
return {
|
| 1164 |
+
"status": "healthy",
|
| 1165 |
"message": "AI Battle Royale server is running",
|
| 1166 |
+
"version": "1.0.0",
|
| 1167 |
+
"timestamp": time.time(),
|
| 1168 |
+
"environment": "huggingface-spaces",
|
| 1169 |
+
"endpoints": {
|
| 1170 |
+
"health": "/health",
|
| 1171 |
+
"models": "/models",
|
| 1172 |
+
"start_game": "/start-game",
|
| 1173 |
+
"stream_game": "/stream-game/{game_id}",
|
| 1174 |
+
"docs": "/docs"
|
| 1175 |
+
},
|
| 1176 |
"note": "API keys are provided by frontend, not environment variables"
|
| 1177 |
}
|
| 1178 |
|
| 1179 |
|
| 1180 |
@app.get("/health")
|
| 1181 |
+
async def health_check(request: Request):
|
| 1182 |
+
"""Comprehensive health check endpoint with system status"""
|
| 1183 |
+
logger.info(f"🏥 Health check accessed from {request.client.host if request.client else 'unknown'}")
|
| 1184 |
+
|
| 1185 |
+
# Perform basic system checks
|
| 1186 |
+
health_status = {
|
| 1187 |
+
"status": "healthy",
|
| 1188 |
+
"message": "AI Battle Royale server is operational",
|
| 1189 |
+
"timestamp": time.time(),
|
| 1190 |
+
"checks": {
|
| 1191 |
+
"server": "healthy",
|
| 1192 |
+
"memory": "ok",
|
| 1193 |
+
"disk": "ok"
|
| 1194 |
+
},
|
| 1195 |
+
"stats": {
|
| 1196 |
+
"active_games": len(active_games),
|
| 1197 |
+
"total_requests": getattr(health_check, 'request_count', 0)
|
| 1198 |
+
},
|
| 1199 |
+
"environment": {
|
| 1200 |
+
"python_version": f"{os.sys.version_info.major}.{os.sys.version_info.minor}.{os.sys.version_info.micro}",
|
| 1201 |
+
"fastapi_available": True,
|
| 1202 |
+
"models_loaded": len(MODEL_PROVIDERS)
|
| 1203 |
+
}
|
| 1204 |
}
|
| 1205 |
+
|
| 1206 |
+
# Increment request counter
|
| 1207 |
+
health_check.request_count = getattr(health_check, 'request_count', 0) + 1
|
| 1208 |
+
|
| 1209 |
+
return health_status
|
| 1210 |
|
| 1211 |
|
| 1212 |
+
@app.options("/{path:path}")
|
| 1213 |
+
async def options_handler(request: Request, path: str):
|
| 1214 |
+
"""Handle CORS preflight requests"""
|
| 1215 |
+
logger.debug(f"🔄 CORS preflight request for path: /{path}")
|
| 1216 |
+
|
| 1217 |
+
return Response(
|
| 1218 |
+
status_code=200,
|
| 1219 |
+
headers={
|
| 1220 |
+
"Access-Control-Allow-Origin": "*",
|
| 1221 |
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
|
| 1222 |
+
"Access-Control-Allow-Headers": "*",
|
| 1223 |
+
"Access-Control-Max-Age": "86400"
|
| 1224 |
+
}
|
| 1225 |
+
)
|
| 1226 |
+
|
| 1227 |
+
|
| 1228 |
+
# Startup event
|
| 1229 |
+
@app.on_event("startup")
|
| 1230 |
+
async def startup_event():
|
| 1231 |
+
"""Log startup information"""
|
| 1232 |
+
logger.info("🚀 AI Battle Royale server starting up...")
|
| 1233 |
+
logger.info(f"📊 Available model providers: {list(MODEL_PROVIDERS.keys())}")
|
| 1234 |
+
logger.info(f"🔧 Debug mode: {os.getenv('DEBUG', 'False')}")
|
| 1235 |
+
logger.info(f"🌐 Server will be available at: http://0.0.0.0:8000")
|
| 1236 |
+
logger.info("✅ Server startup complete!")
|
| 1237 |
+
|
| 1238 |
+
|
| 1239 |
+
@app.on_event("shutdown")
|
| 1240 |
+
async def shutdown_event():
|
| 1241 |
+
"""Clean up on shutdown"""
|
| 1242 |
+
logger.info("🛑 AI Battle Royale server shutting down...")
|
| 1243 |
+
logger.info(f"📈 Final stats: {len(active_games)} active games")
|
| 1244 |
+
logger.info("✅ Server shutdown complete!")
|
| 1245 |
|
| 1246 |
+
|
| 1247 |
+
if __name__ == "__main__":
|
| 1248 |
+
logger.info("🎯 Starting FastAPI server directly...")
|
| 1249 |
+
|
| 1250 |
+
# Enhanced uvicorn configuration for HF Spaces
|
| 1251 |
+
uvicorn.run(
|
| 1252 |
+
app,
|
| 1253 |
+
host="0.0.0.0",
|
| 1254 |
+
port=8000,
|
| 1255 |
+
log_level="info",
|
| 1256 |
+
access_log=True,
|
| 1257 |
+
server_header=False, # Don't expose server version
|
| 1258 |
+
date_header=False, # Don't add date header
|
| 1259 |
+
reload=False, # Disable auto-reload in production
|
| 1260 |
+
workers=1 # Single worker for HF Spaces
|
| 1261 |
+
)
|
frontend/app/page.tsx
CHANGED
|
@@ -146,22 +146,44 @@ const MentalStateBar = ({ label, value, color }: { label: string; value: number;
|
|
| 146 |
)
|
| 147 |
|
| 148 |
const detectServerURL = () => {
|
| 149 |
-
//
|
| 150 |
-
// In production (Docker/HF Spaces), use nginx proxy route
|
| 151 |
if (typeof window !== 'undefined') {
|
| 152 |
-
const
|
| 153 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
if (isLocalDev) {
|
| 156 |
// Local development: connect directly to backend
|
|
|
|
| 157 |
return 'http://localhost:8000'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
} else {
|
| 159 |
// Production (Docker/HF Spaces): always use relative nginx proxy
|
| 160 |
// This ensures the request goes through the nginx proxy on port 7860
|
|
|
|
| 161 |
return '/api'
|
| 162 |
}
|
| 163 |
}
|
| 164 |
// Fallback for SSR - always use relative path in production
|
|
|
|
| 165 |
return '/api'
|
| 166 |
}
|
| 167 |
|
|
@@ -349,29 +371,51 @@ export default function AgentBattleArena() {
|
|
| 349 |
loadLogos()
|
| 350 |
}, [])
|
| 351 |
|
| 352 |
-
//
|
| 353 |
useEffect(() => {
|
| 354 |
const fetchModels = async () => {
|
| 355 |
try {
|
| 356 |
-
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
method: 'GET',
|
| 359 |
headers: {
|
| 360 |
'Accept': 'application/json',
|
| 361 |
-
'Content-Type': 'application/json'
|
|
|
|
| 362 |
},
|
| 363 |
-
|
| 364 |
-
signal: AbortSignal.timeout(10000) // 10 second timeout
|
| 365 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
if (response.ok) {
|
| 367 |
const data = await response.json()
|
| 368 |
-
console.log('Models loaded from server:', data)
|
| 369 |
setAvailableModels(data)
|
| 370 |
} else {
|
| 371 |
-
|
|
|
|
|
|
|
| 372 |
}
|
| 373 |
} catch (error) {
|
| 374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
}
|
| 376 |
}
|
| 377 |
fetchModels()
|
|
@@ -1097,29 +1141,73 @@ export default function AgentBattleArena() {
|
|
| 1097 |
isHuggingFaceSpaces: window.location.hostname.includes('.hf.space') || window.location.hostname.includes('huggingface.co'),
|
| 1098 |
resolvedServerURL: serverURL
|
| 1099 |
}
|
| 1100 |
-
console.log('DEBUG: Environment detection:', environmentInfo)
|
| 1101 |
|
| 1102 |
-
// Step 1:
|
| 1103 |
-
setBattleState((prev) => ({ ...prev, battleLog: [`>
|
| 1104 |
|
| 1105 |
-
//
|
| 1106 |
-
const
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
})
|
| 1115 |
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1119 |
}
|
|
|
|
| 1120 |
const serverInfo = await healthCheckResponse.json()
|
| 1121 |
-
console.log("Server info:", serverInfo)
|
| 1122 |
-
setBattleState((prev) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1123 |
|
| 1124 |
// Step 2: Start the game.
|
| 1125 |
setBattleState((prev) => ({ ...prev, battleLog: [...prev.battleLog, `> Starting game ${gameId}...`, `> Player 1: ${selectedPlayer1Model}`, `> Player 2: ${selectedPlayer2Model}`] }))
|
|
@@ -1171,20 +1259,29 @@ export default function AgentBattleArena() {
|
|
| 1171 |
} catch (error: any) {
|
| 1172 |
console.error("Failed to start battle:", error)
|
| 1173 |
|
| 1174 |
-
//
|
| 1175 |
const errorDetails = [
|
| 1176 |
"> ❌ Battle initialization failed.",
|
| 1177 |
-
`> Environment: ${window.location.hostname.includes('.hf.space') ? 'Hugging Face Spaces' : 'Local/Other'}`,
|
| 1178 |
-
`> Request URL: ${serverURL}`,
|
| 1179 |
-
`> Full URL context: ${window.location.href}`,
|
| 1180 |
-
`> Error: ${error.message}`,
|
| 1181 |
-
">
|
| 1182 |
-
">
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1188 |
]
|
| 1189 |
|
| 1190 |
setBattleState((prev) => ({
|
|
@@ -1470,48 +1567,90 @@ export default function AgentBattleArena() {
|
|
| 1470 |
TEST SOUNDS
|
| 1471 |
</Button>
|
| 1472 |
|
| 1473 |
-
{/* Test Connection Button */}
|
| 1474 |
<Button
|
| 1475 |
onClick={async () => {
|
| 1476 |
-
console.log('Testing server connectivity...')
|
| 1477 |
setBattleState(prev => ({
|
| 1478 |
...prev,
|
| 1479 |
-
battleLog: [`>
|
| 1480 |
}))
|
| 1481 |
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
| 1487 |
-
|
| 1488 |
-
|
| 1489 |
-
|
| 1490 |
-
|
| 1491 |
-
|
| 1492 |
-
|
| 1493 |
-
const data = await response.json()
|
| 1494 |
setBattleState(prev => ({
|
| 1495 |
...prev,
|
| 1496 |
-
battleLog: [...prev.battleLog, `>
|
| 1497 |
}))
|
| 1498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1499 |
setBattleState(prev => ({
|
| 1500 |
...prev,
|
| 1501 |
-
battleLog: [
|
|
|
|
|
|
|
|
|
|
| 1502 |
}))
|
| 1503 |
}
|
| 1504 |
-
} catch (error: any) {
|
| 1505 |
-
setBattleState(prev => ({
|
| 1506 |
-
...prev,
|
| 1507 |
-
battleLog: [...prev.battleLog, `> ❌ Connection error: ${error.message}`]
|
| 1508 |
-
}))
|
| 1509 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1510 |
}}
|
| 1511 |
variant="outline"
|
| 1512 |
className="border-gray-600 text-gray-300 hover:bg-gray-700"
|
| 1513 |
>
|
| 1514 |
-
TEST CONNECTION
|
| 1515 |
</Button>
|
| 1516 |
|
| 1517 |
{/* Test Animation Button */}
|
|
|
|
| 146 |
)
|
| 147 |
|
| 148 |
const detectServerURL = () => {
|
| 149 |
+
// Enhanced server URL detection with comprehensive debugging
|
|
|
|
| 150 |
if (typeof window !== 'undefined') {
|
| 151 |
+
const hostname = window.location.hostname
|
| 152 |
+
const port = window.location.port
|
| 153 |
+
const protocol = window.location.protocol
|
| 154 |
+
const href = window.location.href
|
| 155 |
+
|
| 156 |
+
const isLocalDev = hostname === 'localhost' && port === '3000'
|
| 157 |
+
const isHuggingFaceSpaces = hostname.includes('.hf.space') || hostname.includes('huggingface.co')
|
| 158 |
+
const isDockerLocal = hostname === 'localhost' && port === '7860'
|
| 159 |
+
|
| 160 |
+
console.log('🔍 Environment Detection:', {
|
| 161 |
+
hostname,
|
| 162 |
+
port,
|
| 163 |
+
protocol,
|
| 164 |
+
href,
|
| 165 |
+
isLocalDev,
|
| 166 |
+
isHuggingFaceSpaces,
|
| 167 |
+
isDockerLocal
|
| 168 |
+
})
|
| 169 |
|
| 170 |
if (isLocalDev) {
|
| 171 |
// Local development: connect directly to backend
|
| 172 |
+
console.log('📍 Using local development backend')
|
| 173 |
return 'http://localhost:8000'
|
| 174 |
+
} else if (isDockerLocal) {
|
| 175 |
+
// Local Docker: use nginx proxy
|
| 176 |
+
console.log('📍 Using local Docker nginx proxy')
|
| 177 |
+
return '/api'
|
| 178 |
} else {
|
| 179 |
// Production (Docker/HF Spaces): always use relative nginx proxy
|
| 180 |
// This ensures the request goes through the nginx proxy on port 7860
|
| 181 |
+
console.log('📍 Using production nginx proxy')
|
| 182 |
return '/api'
|
| 183 |
}
|
| 184 |
}
|
| 185 |
// Fallback for SSR - always use relative path in production
|
| 186 |
+
console.log('📍 Using SSR fallback')
|
| 187 |
return '/api'
|
| 188 |
}
|
| 189 |
|
|
|
|
| 371 |
loadLogos()
|
| 372 |
}, [])
|
| 373 |
|
| 374 |
+
// Enhanced model fetching with comprehensive error handling
|
| 375 |
useEffect(() => {
|
| 376 |
const fetchModels = async () => {
|
| 377 |
try {
|
| 378 |
+
const url = `${serverURL}/models`
|
| 379 |
+
console.log('🔄 Fetching models from server...', url)
|
| 380 |
+
console.log('🌐 Server URL configuration:', serverURL)
|
| 381 |
+
|
| 382 |
+
const controller = new AbortController()
|
| 383 |
+
const timeoutId = setTimeout(() => {
|
| 384 |
+
console.warn('⏰ Request timeout, aborting...')
|
| 385 |
+
controller.abort()
|
| 386 |
+
}, 15000) // 15 second timeout
|
| 387 |
+
|
| 388 |
+
const response = await fetch(url, {
|
| 389 |
method: 'GET',
|
| 390 |
headers: {
|
| 391 |
'Accept': 'application/json',
|
| 392 |
+
'Content-Type': 'application/json',
|
| 393 |
+
'Cache-Control': 'no-cache'
|
| 394 |
},
|
| 395 |
+
signal: controller.signal
|
|
|
|
| 396 |
})
|
| 397 |
+
|
| 398 |
+
clearTimeout(timeoutId)
|
| 399 |
+
|
| 400 |
+
console.log('📡 Response status:', response.status)
|
| 401 |
+
console.log('📋 Response headers:', Object.fromEntries(response.headers.entries()))
|
| 402 |
+
|
| 403 |
if (response.ok) {
|
| 404 |
const data = await response.json()
|
| 405 |
+
console.log('✅ Models loaded from server:', data)
|
| 406 |
setAvailableModels(data)
|
| 407 |
} else {
|
| 408 |
+
const errorText = await response.text().catch(() => 'Unable to read error response')
|
| 409 |
+
console.warn(`❌ Failed to fetch models from server (${response.status}): ${errorText}`)
|
| 410 |
+
console.warn('🔄 Using fallback model configuration')
|
| 411 |
}
|
| 412 |
} catch (error) {
|
| 413 |
+
if (error.name === 'AbortError') {
|
| 414 |
+
console.warn("⏰ Model fetch request timed out")
|
| 415 |
+
} else {
|
| 416 |
+
console.warn("❌ Failed to fetch available models from server:", error)
|
| 417 |
+
}
|
| 418 |
+
console.warn('🔄 Using fallback model configuration')
|
| 419 |
}
|
| 420 |
}
|
| 421 |
fetchModels()
|
|
|
|
| 1141 |
isHuggingFaceSpaces: window.location.hostname.includes('.hf.space') || window.location.hostname.includes('huggingface.co'),
|
| 1142 |
resolvedServerURL: serverURL
|
| 1143 |
}
|
| 1144 |
+
console.log('🔍 DEBUG: Environment detection:', environmentInfo)
|
| 1145 |
|
| 1146 |
+
// Step 1: Enhanced health check with comprehensive diagnostics
|
| 1147 |
+
setBattleState((prev) => ({ ...prev, battleLog: [`> 🔍 Testing connectivity to ${serverURL}...`] }))
|
| 1148 |
|
| 1149 |
+
// Test multiple endpoints to diagnose connectivity issues
|
| 1150 |
+
const healthEndpoints = [
|
| 1151 |
+
{ name: 'Root', url: serverURL },
|
| 1152 |
+
{ name: 'Health', url: `${serverURL}/health` },
|
| 1153 |
+
{ name: 'Models', url: `${serverURL}/models` }
|
| 1154 |
+
]
|
| 1155 |
+
|
| 1156 |
+
let healthCheckResponse
|
| 1157 |
+
let workingEndpoint = null
|
|
|
|
| 1158 |
|
| 1159 |
+
for (const endpoint of healthEndpoints) {
|
| 1160 |
+
try {
|
| 1161 |
+
console.log(`🔍 Testing ${endpoint.name} endpoint: ${endpoint.url}`)
|
| 1162 |
+
setBattleState((prev) => ({ ...prev, battleLog: [...prev.battleLog, `> Testing ${endpoint.name}: ${endpoint.url}`] }))
|
| 1163 |
+
|
| 1164 |
+
const controller = new AbortController()
|
| 1165 |
+
const timeoutId = setTimeout(() => controller.abort(), 10000)
|
| 1166 |
+
|
| 1167 |
+
const response = await fetch(endpoint.url, {
|
| 1168 |
+
method: 'GET',
|
| 1169 |
+
headers: {
|
| 1170 |
+
'Accept': 'application/json',
|
| 1171 |
+
'Content-Type': 'application/json',
|
| 1172 |
+
'Cache-Control': 'no-cache'
|
| 1173 |
+
},
|
| 1174 |
+
signal: controller.signal
|
| 1175 |
+
})
|
| 1176 |
+
|
| 1177 |
+
clearTimeout(timeoutId)
|
| 1178 |
+
|
| 1179 |
+
console.log(`📡 ${endpoint.name} response:`, response.status, response.statusText)
|
| 1180 |
+
|
| 1181 |
+
if (response.ok) {
|
| 1182 |
+
healthCheckResponse = response
|
| 1183 |
+
workingEndpoint = endpoint
|
| 1184 |
+
setBattleState((prev) => ({ ...prev, battleLog: [...prev.battleLog, `> ✅ ${endpoint.name} endpoint working!`] }))
|
| 1185 |
+
break
|
| 1186 |
+
} else {
|
| 1187 |
+
setBattleState((prev) => ({ ...prev, battleLog: [...prev.battleLog, `> ❌ ${endpoint.name} failed: ${response.status}`] }))
|
| 1188 |
+
}
|
| 1189 |
+
} catch (error) {
|
| 1190 |
+
console.warn(`❌ ${endpoint.name} endpoint failed:`, error.message)
|
| 1191 |
+
setBattleState((prev) => ({ ...prev, battleLog: [...prev.battleLog, `> ❌ ${endpoint.name} error: ${error.message}`] }))
|
| 1192 |
+
}
|
| 1193 |
+
}
|
| 1194 |
+
|
| 1195 |
+
if (!healthCheckResponse || !healthCheckResponse.ok) {
|
| 1196 |
+
throw new Error('All health check endpoints failed - server may be unreachable')
|
| 1197 |
}
|
| 1198 |
+
|
| 1199 |
const serverInfo = await healthCheckResponse.json()
|
| 1200 |
+
console.log("✅ Server info:", serverInfo)
|
| 1201 |
+
setBattleState((prev) => ({
|
| 1202 |
+
...prev,
|
| 1203 |
+
battleLog: [
|
| 1204 |
+
...prev.battleLog,
|
| 1205 |
+
`> ✅ Server online: ${serverInfo.message}`,
|
| 1206 |
+
`> Working endpoint: ${workingEndpoint.name}`,
|
| 1207 |
+
`> Server version: ${serverInfo.version || 'unknown'}`,
|
| 1208 |
+
`> Environment: ${serverInfo.environment || 'unknown'}`
|
| 1209 |
+
]
|
| 1210 |
+
}))
|
| 1211 |
|
| 1212 |
// Step 2: Start the game.
|
| 1213 |
setBattleState((prev) => ({ ...prev, battleLog: [...prev.battleLog, `> Starting game ${gameId}...`, `> Player 1: ${selectedPlayer1Model}`, `> Player 2: ${selectedPlayer2Model}`] }))
|
|
|
|
| 1259 |
} catch (error: any) {
|
| 1260 |
console.error("Failed to start battle:", error)
|
| 1261 |
|
| 1262 |
+
// Comprehensive error reporting with detailed diagnostics
|
| 1263 |
const errorDetails = [
|
| 1264 |
"> ❌ Battle initialization failed.",
|
| 1265 |
+
`> 🌍 Environment: ${window.location.hostname.includes('.hf.space') ? 'Hugging Face Spaces' : 'Local/Other'}`,
|
| 1266 |
+
`> 🔗 Request URL: ${serverURL}`,
|
| 1267 |
+
`> 📍 Full URL context: ${window.location.href}`,
|
| 1268 |
+
`> ❌ Error: ${error.message}`,
|
| 1269 |
+
"> ",
|
| 1270 |
+
"> 🔍 Diagnostic Information:",
|
| 1271 |
+
`> - Protocol: ${window.location.protocol}`,
|
| 1272 |
+
`> - Hostname: ${window.location.hostname}`,
|
| 1273 |
+
`> - Port: ${window.location.port || 'default'}`,
|
| 1274 |
+
`> - User Agent: ${navigator.userAgent.substring(0, 50)}...`,
|
| 1275 |
+
"> ",
|
| 1276 |
+
"> 🛠️ Possible Solutions:",
|
| 1277 |
+
"> 1. Check if backend server is running (port 8000)",
|
| 1278 |
+
"> 2. Verify nginx proxy configuration",
|
| 1279 |
+
"> 3. Confirm API endpoint paths are correct",
|
| 1280 |
+
"> 4. Check CORS policy settings",
|
| 1281 |
+
"> 5. Verify network connectivity",
|
| 1282 |
+
"> 6. Validate API keys and model availability",
|
| 1283 |
+
"> 7. Try refreshing the page",
|
| 1284 |
+
"> 8. Check browser console for additional errors"
|
| 1285 |
]
|
| 1286 |
|
| 1287 |
setBattleState((prev) => ({
|
|
|
|
| 1567 |
TEST SOUNDS
|
| 1568 |
</Button>
|
| 1569 |
|
| 1570 |
+
{/* Enhanced Test Connection Button */}
|
| 1571 |
<Button
|
| 1572 |
onClick={async () => {
|
| 1573 |
+
console.log('🔍 Testing server connectivity comprehensively...')
|
| 1574 |
setBattleState(prev => ({
|
| 1575 |
...prev,
|
| 1576 |
+
battleLog: [`> 🔍 Starting comprehensive connectivity test...`]
|
| 1577 |
}))
|
| 1578 |
|
| 1579 |
+
const testEndpoints = [
|
| 1580 |
+
{ name: 'Root', path: '' },
|
| 1581 |
+
{ name: 'Health', path: '/health' },
|
| 1582 |
+
{ name: 'Models', path: '/models' }
|
| 1583 |
+
]
|
| 1584 |
+
|
| 1585 |
+
for (const endpoint of testEndpoints) {
|
| 1586 |
+
try {
|
| 1587 |
+
const url = `${serverURL}${endpoint.path}`
|
| 1588 |
+
console.log(`🔍 Testing ${endpoint.name}: ${url}`)
|
| 1589 |
+
|
|
|
|
| 1590 |
setBattleState(prev => ({
|
| 1591 |
...prev,
|
| 1592 |
+
battleLog: [...prev.battleLog, `> Testing ${endpoint.name}: ${url}`]
|
| 1593 |
}))
|
| 1594 |
+
|
| 1595 |
+
const controller = new AbortController()
|
| 1596 |
+
const timeoutId = setTimeout(() => controller.abort(), 8000)
|
| 1597 |
+
|
| 1598 |
+
const startTime = performance.now()
|
| 1599 |
+
const response = await fetch(url, {
|
| 1600 |
+
method: 'GET',
|
| 1601 |
+
headers: {
|
| 1602 |
+
'Accept': 'application/json',
|
| 1603 |
+
'Content-Type': 'application/json',
|
| 1604 |
+
'Cache-Control': 'no-cache'
|
| 1605 |
+
},
|
| 1606 |
+
signal: controller.signal
|
| 1607 |
+
})
|
| 1608 |
+
const responseTime = Math.round(performance.now() - startTime)
|
| 1609 |
+
|
| 1610 |
+
clearTimeout(timeoutId)
|
| 1611 |
+
|
| 1612 |
+
if (response.ok) {
|
| 1613 |
+
const data = await response.json().catch(() => ({ message: 'Response not JSON' }))
|
| 1614 |
+
setBattleState(prev => ({
|
| 1615 |
+
...prev,
|
| 1616 |
+
battleLog: [
|
| 1617 |
+
...prev.battleLog,
|
| 1618 |
+
`> ✅ ${endpoint.name} OK (${responseTime}ms)`,
|
| 1619 |
+
`> Status: ${response.status}`,
|
| 1620 |
+
`> Message: ${data.message || 'Connected'}`
|
| 1621 |
+
]
|
| 1622 |
+
}))
|
| 1623 |
+
} else {
|
| 1624 |
+
setBattleState(prev => ({
|
| 1625 |
+
...prev,
|
| 1626 |
+
battleLog: [
|
| 1627 |
+
...prev.battleLog,
|
| 1628 |
+
`> ❌ ${endpoint.name} Failed (${responseTime}ms)`,
|
| 1629 |
+
`> Status: ${response.status} ${response.statusText}`
|
| 1630 |
+
]
|
| 1631 |
+
}))
|
| 1632 |
+
}
|
| 1633 |
+
} catch (error: any) {
|
| 1634 |
+
const errorType = error.name === 'AbortError' ? 'Timeout' : 'Error'
|
| 1635 |
setBattleState(prev => ({
|
| 1636 |
...prev,
|
| 1637 |
+
battleLog: [
|
| 1638 |
+
...prev.battleLog,
|
| 1639 |
+
`> ❌ ${endpoint.name} ${errorType}: ${error.message}`
|
| 1640 |
+
]
|
| 1641 |
}))
|
| 1642 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1643 |
}
|
| 1644 |
+
|
| 1645 |
+
setBattleState(prev => ({
|
| 1646 |
+
...prev,
|
| 1647 |
+
battleLog: [...prev.battleLog, `> 🏁 Connectivity test complete`]
|
| 1648 |
+
}))
|
| 1649 |
}}
|
| 1650 |
variant="outline"
|
| 1651 |
className="border-gray-600 text-gray-300 hover:bg-gray-700"
|
| 1652 |
>
|
| 1653 |
+
🔍 TEST CONNECTION
|
| 1654 |
</Button>
|
| 1655 |
|
| 1656 |
{/* Test Animation Button */}
|