fix: update Swapper endpoints (/health, /models), remove upload size limits, auto-convert images
This commit is contained in:
@@ -158,7 +158,7 @@ export function AgentAvatarUpload({
|
||||
{canEdit && (
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-white/50 mb-3">
|
||||
Upload a custom avatar for this agent. Recommended size: 256x256px.
|
||||
Upload a custom avatar for this agent. Any image format accepted.
|
||||
</p>
|
||||
<input
|
||||
ref={inputRef}
|
||||
|
||||
@@ -200,21 +200,26 @@ class NodeGuardian:
|
||||
# Collect Swapper Metrics
|
||||
swapper_url = os.getenv("SWAPPER_URL", "http://swapper-service:8890")
|
||||
try:
|
||||
# Check healthz
|
||||
# Check health (Swapper uses /health, not /healthz)
|
||||
try:
|
||||
r = await self.client.get(f"{swapper_url}/healthz", timeout=3.0)
|
||||
metrics["swapper_healthy"] = (r.status_code == 200)
|
||||
r = await self.client.get(f"{swapper_url}/health", timeout=3.0)
|
||||
if r.status_code == 200:
|
||||
health_data = r.json()
|
||||
metrics["swapper_healthy"] = health_data.get("status") == "healthy"
|
||||
else:
|
||||
metrics["swapper_healthy"] = False
|
||||
except Exception:
|
||||
metrics["swapper_healthy"] = False
|
||||
|
||||
# Check models
|
||||
# Check models (Swapper uses /models, not /v1/models)
|
||||
try:
|
||||
r = await self.client.get(f"{swapper_url}/v1/models", timeout=5.0)
|
||||
r = await self.client.get(f"{swapper_url}/models", timeout=5.0)
|
||||
if r.status_code == 200:
|
||||
data = r.json()
|
||||
models = data.get("models", [])
|
||||
metrics["swapper_models_total"] = len(models)
|
||||
metrics["swapper_models_loaded"] = sum(1 for m in models if m.get("loaded") is True)
|
||||
# Swapper uses "status": "loaded" not "loaded": true
|
||||
metrics["swapper_models_loaded"] = sum(1 for m in models if m.get("status") == "loaded")
|
||||
metrics["swapper_state"] = data
|
||||
except Exception as e:
|
||||
# logger.warning(f"Failed to fetch Swapper models: {e}")
|
||||
|
||||
@@ -39,3 +39,4 @@ echo " docker-compose -f docker-compose.city-space.yml logs -f"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,3 +42,4 @@ echo " ./scripts/test-phase2-e2e.sh"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -51,3 +51,4 @@ echo ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,3 +55,4 @@ echo "📚 Documentation: docs/PHASE4_READY.md"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,3 +14,4 @@ echo "✅ Services stopped!"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,3 +17,4 @@ echo " docker-compose -f docker-compose.agents.yml down -v"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,3 +16,4 @@ echo ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,3 +11,4 @@ echo "✅ Phase 4 services stopped"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -204,3 +204,4 @@ fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,3 +23,4 @@ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7001"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -346,3 +346,4 @@ Proprietary — DAARION Ecosystem
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,41 +69,32 @@ AUTH_SERVICE_URL = os.getenv("AUTH_SERVICE_URL", "http://daarion-auth:7020")
|
||||
MATRIX_GATEWAY_URL = os.getenv("MATRIX_GATEWAY_URL", "http://daarion-matrix-gateway:7025")
|
||||
|
||||
# Helper for image processing
|
||||
def process_image(image_bytes: bytes, target_size: tuple = (256, 256)) -> tuple[bytes, bytes]:
|
||||
def process_image(image_bytes: bytes, max_size: int = 1024, force_square: bool = False) -> tuple[bytes, bytes]:
|
||||
"""
|
||||
Process image:
|
||||
1. Convert to PNG
|
||||
2. Resize/Crop to target_size (default 256x256)
|
||||
3. Generate thumbnail 128x128
|
||||
1. Convert to PNG (any format accepted)
|
||||
2. Resize to fit within max_size (preserving aspect ratio)
|
||||
3. Optionally force square crop for avatars/logos
|
||||
4. Generate thumbnail 128x128
|
||||
Returns (processed_bytes, thumb_bytes)
|
||||
"""
|
||||
with Image.open(io.BytesIO(image_bytes)) as img:
|
||||
# Convert to RGBA/RGB
|
||||
if img.mode in ('P', 'CMYK'):
|
||||
if img.mode in ('P', 'CMYK', 'LA'):
|
||||
img = img.convert('RGBA')
|
||||
elif img.mode != 'RGBA':
|
||||
img = img.convert('RGB')
|
||||
|
||||
# Resize/Crop to target_size
|
||||
img_ratio = img.width / img.height
|
||||
target_ratio = target_size[0] / target_size[1]
|
||||
|
||||
if img_ratio > target_ratio:
|
||||
# Wider than target
|
||||
new_height = target_size[1]
|
||||
new_width = int(new_height * img_ratio)
|
||||
else:
|
||||
# Taller than target
|
||||
new_width = target_size[0]
|
||||
new_height = int(new_width / img_ratio)
|
||||
# Force square crop if needed (for avatars/logos)
|
||||
if force_square:
|
||||
min_dim = min(img.width, img.height)
|
||||
left = (img.width - min_dim) / 2
|
||||
top = (img.height - min_dim) / 2
|
||||
img = img.crop((left, top, left + min_dim, top + min_dim))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# Center crop
|
||||
left = (new_width - target_size[0]) / 2
|
||||
top = (new_height - target_size[1]) / 2
|
||||
right = (new_width + target_size[0]) / 2
|
||||
bottom = (new_height + target_size[1]) / 2
|
||||
|
||||
img = img.crop((left, top, right, bottom))
|
||||
# Resize to fit within max_size (preserve aspect ratio)
|
||||
if img.width > max_size or img.height > max_size:
|
||||
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
||||
|
||||
# Save processed
|
||||
processed_io = io.BytesIO()
|
||||
@@ -318,25 +309,31 @@ async def update_agent_visibility_endpoint(
|
||||
@router.post("/assets/upload")
|
||||
async def upload_asset(
|
||||
file: UploadFile = File(...),
|
||||
type: str = Form(...) # microdao_logo, microdao_banner, room_logo, room_banner
|
||||
type: str = Form(...) # microdao_logo, microdao_banner, room_logo, room_banner, agent_avatar
|
||||
):
|
||||
"""Upload asset (logo/banner) with auto-processing"""
|
||||
"""Upload asset (logo/banner/avatar) with auto-processing. Accepts any image format."""
|
||||
try:
|
||||
# Validate type
|
||||
if type not in ['microdao_logo', 'microdao_banner', 'room_logo', 'room_banner']:
|
||||
raise HTTPException(status_code=400, detail="Invalid asset type")
|
||||
valid_types = ['microdao_logo', 'microdao_banner', 'room_logo', 'room_banner', 'agent_avatar']
|
||||
if type not in valid_types:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid asset type. Valid: {valid_types}")
|
||||
|
||||
# Validate file size (5MB limit) - done by reading content
|
||||
# Validate file size (20MB limit)
|
||||
content = await file.read()
|
||||
if len(content) > 5 * 1024 * 1024:
|
||||
raise HTTPException(status_code=400, detail="File too large (max 5MB)")
|
||||
if len(content) > 20 * 1024 * 1024:
|
||||
raise HTTPException(status_code=400, detail="File too large (max 20MB)")
|
||||
|
||||
# Process image
|
||||
target_size = (256, 256)
|
||||
# Process image based on type
|
||||
# Logos and avatars: square, max 512px
|
||||
# Banners: max 1920px width, preserve aspect ratio
|
||||
if 'banner' in type:
|
||||
target_size = (1200, 400) # Standard banner size
|
||||
max_size = 1920
|
||||
force_square = False
|
||||
else:
|
||||
max_size = 512
|
||||
force_square = True # Square crop for logos/avatars
|
||||
|
||||
processed_bytes, thumb_bytes = process_image(content, target_size=target_size)
|
||||
processed_bytes, thumb_bytes = process_image(content, max_size=max_size, force_square=force_square)
|
||||
|
||||
# Save to disk
|
||||
filename = f"{uuid.uuid4()}.png"
|
||||
@@ -4078,9 +4075,10 @@ async def get_node_swapper_detail(node_id: str):
|
||||
models = [
|
||||
SwapperModel(
|
||||
name=m.get("name", "unknown"),
|
||||
loaded=m.get("loaded", False),
|
||||
# Swapper uses "status": "loaded" not "loaded": true
|
||||
loaded=m.get("status") == "loaded" or m.get("loaded", False),
|
||||
type=m.get("type"),
|
||||
vram_gb=m.get("vram_gb")
|
||||
vram_gb=m.get("size_gb") or m.get("vram_gb")
|
||||
)
|
||||
for m in models_data
|
||||
]
|
||||
|
||||
@@ -161,3 +161,4 @@ async def agents_presence_generator():
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user