LogicGoInfotechSpaces commited on
Commit
ae10bc2
·
1 Parent(s): 0fc8432

API: add FastAPI endpoints for Hair Swap flow; deps updated

Browse files
Files changed (3) hide show
  1. README.md +16 -1
  2. api/main.py +100 -0
  3. requirements.txt +4 -1
README.md CHANGED
@@ -11,4 +11,19 @@ license: mit
11
  hardware: cpu-basic
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  hardware: cpu-basic
12
  ---
13
 
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+
16
+ ## API (FastAPI)
17
+ - Base URL: `https://logicgoinfotechspaces-hair-stable-new.hf.space`
18
+ - Auth: Bearer token `logicgo@123`
19
+
20
+ Endpoints:
21
+ - `GET /health` → {"status":"healthy"}
22
+ - `POST /upload` (form-data: image=file) → {"id":"<id>","filename":"name.png"}
23
+ - `POST /get-hairswap` (JSON body with source_id, reference_id, …) → {"result":"output_xxx.png"}
24
+ - `GET /download/{filename}` → image file
25
+ - `GET /logs` → recent uploads/results
26
+
27
+ Local run:
28
+ - Install deps: `python3 -m pip install -r requirements.txt`
29
+ - Run API: `python3 -m uvicorn api.main:app --host 0.0.0.0 --port 7860`
api/main.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import shutil
4
+ from datetime import datetime
5
+ from typing import Dict, List, Optional
6
+
7
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Header
8
+ from fastapi.responses import FileResponse, JSONResponse
9
+ from pydantic import BaseModel
10
+
11
+ # Simple settings
12
+ UPLOAD_DIR = os.path.join(os.getcwd(), "uploads")
13
+ OUTPUT_DIR = os.path.join(os.getcwd(), "outputs")
14
+ AUTH_TOKEN = "logicgo@123" # Bearer token
15
+
16
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
17
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
18
+
19
+ app = FastAPI(title="Hair Swap API", version="1.0.0")
20
+
21
+ # In-memory stores
22
+ file_store: Dict[str, Dict[str, str]] = {}
23
+ logs: List[Dict[str, str]] = []
24
+
25
+
26
+ def bearer_auth(authorization: Optional[str] = Header(default=None)) -> None:
27
+ if authorization is None or not authorization.lower().startswith("bearer "):
28
+ raise HTTPException(status_code=401, detail="Unauthorized")
29
+ token = authorization.split(" ", 1)[1]
30
+ if token != AUTH_TOKEN:
31
+ raise HTTPException(status_code=403, detail="Forbidden")
32
+
33
+
34
+ class HairSwapRequest(BaseModel):
35
+ source_id: str
36
+ reference_id: str
37
+ converter_scale: Optional[float] = 1.0
38
+ scale: Optional[float] = 1.0
39
+ guidance_scale: Optional[float] = 1.5
40
+ controlnet_conditioning_scale: Optional[float] = 1.0
41
+
42
+
43
+ @app.get("/health")
44
+ def health() -> Dict[str, str]:
45
+ return {"status": "healthy"}
46
+
47
+
48
+ @app.post("/upload")
49
+ def upload_image(image: UploadFile = File(...), _: None = Depends(bearer_auth)) -> Dict[str, str]:
50
+ ext = os.path.splitext(image.filename)[1] or ".png"
51
+ file_id = str(uuid.uuid4())
52
+ stored_name = f"{file_id}{ext}"
53
+ stored_path = os.path.join(UPLOAD_DIR, stored_name)
54
+
55
+ with open(stored_path, "wb") as f:
56
+ shutil.copyfileobj(image.file, f)
57
+
58
+ file_store[file_id] = {
59
+ "filename": image.filename,
60
+ "stored_name": stored_name,
61
+ "path": stored_path,
62
+ "timestamp": datetime.utcnow().isoformat(),
63
+ }
64
+ logs.append({"id": file_id, "filename": image.filename, "timestamp": datetime.utcnow().isoformat()})
65
+ return {"id": file_id, "filename": image.filename}
66
+
67
+
68
+ @app.post("/get-hairswap")
69
+ def get_hairswap(req: HairSwapRequest, _: None = Depends(bearer_auth)) -> Dict[str, str]:
70
+ if req.source_id not in file_store:
71
+ raise HTTPException(status_code=404, detail="source_id not found")
72
+ if req.reference_id not in file_store:
73
+ raise HTTPException(status_code=404, detail="reference_id not found")
74
+
75
+ # Placeholder: In lieu of a real hair-swap model, return the source image as the result
76
+ src_path = file_store[req.source_id]["path"]
77
+ result_name = f"output_{uuid.uuid4().hex}.png"
78
+ result_path = os.path.join(OUTPUT_DIR, result_name)
79
+ shutil.copyfile(src_path, result_path)
80
+
81
+ logs.append({"result": result_name, "timestamp": datetime.utcnow().isoformat()})
82
+ return {"result": result_name}
83
+
84
+
85
+ @app.get("/download/{filename}")
86
+ def download_file(filename: str, _: None = Depends(bearer_auth)):
87
+ # Serve from outputs first, then uploads as a fallback
88
+ path = os.path.join(OUTPUT_DIR, filename)
89
+ if not os.path.isfile(path):
90
+ alt = os.path.join(UPLOAD_DIR, filename)
91
+ if os.path.isfile(alt):
92
+ path = alt
93
+ else:
94
+ raise HTTPException(status_code=404, detail="file not found")
95
+ return FileResponse(path)
96
+
97
+
98
+ @app.get("/logs")
99
+ def get_logs(_: None = Depends(bearer_auth)) -> JSONResponse:
100
+ return JSONResponse(content=logs)
requirements.txt CHANGED
@@ -22,4 +22,7 @@ kornia==0.5.0
22
  webdataset
23
  packaging
24
  wldhx.yadisk-direct
25
- altair<5
 
 
 
 
22
  webdataset
23
  packaging
24
  wldhx.yadisk-direct
25
+ altair<5
26
+ fastapi
27
+ uvicorn[standard]
28
+ python-multipart