manual submit

This commit is contained in:
Khoa (Revenovich) Tran Gia
2026-03-07 21:49:16 +07:00
parent 1748cbf8d2
commit 6004b000a7
39 changed files with 5794 additions and 614 deletions

View File

@@ -3,10 +3,11 @@ from __future__ import annotations
import logging
import mimetypes
import re
from pathlib import Path
from typing import Optional
from fastapi import APIRouter, Body, Depends, File, Form, HTTPException, UploadFile
from fastapi import APIRouter, Body, Depends, File, Form, HTTPException, Query, UploadFile
from fastapi.responses import Response
from web.auth import require_auth
@@ -15,10 +16,38 @@ from web.deps import get_config, get_user_registry
router = APIRouter()
logger = logging.getLogger(__name__)
_ALLOWED_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}
_MAX_UPLOAD_BYTES = 50 * 1024 * 1024 # 50 MB
_SAFE_SLOT_RE = re.compile(r'^[a-zA-Z0-9_\-]+$')
def _validate_slot_key(slot_key: str) -> None:
if not _SAFE_SLOT_RE.match(slot_key):
raise HTTPException(400, "slot_key may only contain letters, digits, hyphens, underscores")
@router.get("")
async def list_inputs(_: dict = Depends(require_auth)):
"""List all input images (Discord + web uploads)."""
async def list_inputs(
_: dict = Depends(require_auth),
persons: list[str] = Query(default=[], alias="persons", description="Filter by person name/alias substring (repeatable)"),
):
"""List all input images (Discord + web uploads). Optionally filter by persons."""
active_persons = [p.strip() for p in persons if p.strip()]
if active_persons:
import face_db as face_db_mod
from input_image_db import get_images_by_ids
all_ids: set[int] = set()
for p in active_persons:
ids = face_db_mod.get_source_ids_for_person_query(p, "input")
all_ids.update(ids)
images = list(get_images_by_ids(list(all_ids))) if all_ids else []
if images:
person_map = face_db_mod.get_persons_for_source_id_map(
[img["id"] for img in images], "input"
)
for img in images:
img["detected_persons"] = person_map.get(img["id"], [])
return images
from input_image_db import get_all_images
rows = get_all_images()
return [dict(r) for r in rows]
@@ -44,8 +73,16 @@ async def upload_input(
if config is None:
raise HTTPException(503, "Config not available")
if slot_key:
_validate_slot_key(slot_key)
data = await file.read()
filename = file.filename or "upload.png"
ext = Path(filename).suffix.lower()
if ext not in _ALLOWED_EXTS:
raise HTTPException(415, f"Unsupported file type '{ext}'. Allowed: {sorted(_ALLOWED_EXTS)}")
if len(data) > _MAX_UPLOAD_BYTES:
raise HTTPException(413, "File too large (max 50 MB)")
from input_image_db import upsert_image, activate_image_for_slot
row_id = upsert_image(
@@ -72,7 +109,30 @@ async def upload_input(
if comfy:
comfy.state_manager.set_override(slot_key, activated_filename)
return {"id": row_id, "filename": filename, "slot_key": slot_key, "activated_filename": activated_filename}
# Face scan — runs synchronously here (~1-2 s); unknown faces returned to UI
pending_faces: list[dict] = []
try:
from face_service import get_face_service
import face_db as _face_db
_face_db.init_db()
svc = get_face_service()
if svc.available:
results = await svc.scan_input_image(row_id, data)
pending_faces = [
{"detection_id": r.detection_id, "face_index": r.face_index, "bbox": r.bbox}
for r in results
if r.matched_person_id is None
]
except Exception as exc:
logger.warning("Face scan failed for upload row_id=%d: %s", row_id, exc)
return {
"id": row_id,
"filename": filename,
"slot_key": slot_key,
"activated_filename": activated_filename,
"pending_faces": pending_faces,
}
@router.post("/{row_id}/activate")
@@ -92,6 +152,7 @@ async def activate_input(
raise HTTPException(404, "Image not found")
user_label: str = user["sub"]
_validate_slot_key(slot_key)
namespaced_key = f"{user_label}_{slot_key}"
try: