Spaces:
Runtime error
Runtime error
Commit
·
aabae8d
0
Parent(s):
feat: First commit
Browse files- .dockerignore +1 -0
- .gitignore +7 -0
- Dockerfile +11 -0
- README.md +65 -0
- apps/apis/__init__.py +13 -0
- apps/apis/chats/__init__.py +33 -0
- apps/apis/fd/__init__.py +14 -0
- apps/apis/img2text/__init__.py +14 -0
- apps/apis/rembg/__init__.py +57 -0
- apps/apis/rembg/controllers/rembg_controller.py +9 -0
- apps/apis/rembg/models/rembg_model.py +18 -0
- apps/create_app.py +17 -0
- apps/services/__init__.py +7 -0
- apps/services/foward/__init__.py +31 -0
- apps/services/foward/fw_middleware.py +77 -0
- apps/services/foward/fw_storage.py +46 -0
- apps/services/foward/models/fw_model.py +9 -0
- docker-compose.yaml +13 -0
- main.py +18 -0
- requirements.txt +0 -0
- temp/.gitkeep +0 -0
- utils/constants.py +38 -0
- utils/local_storage.py +81 -0
- utils/metadata.py +34 -0
.dockerignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
env/
|
.gitignore
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
env/
|
2 |
+
__pycache__/
|
3 |
+
.env
|
4 |
+
temp/*
|
5 |
+
|
6 |
+
# Keep the .gitkeep file in the temp/ directory
|
7 |
+
!.gitkeep
|
Dockerfile
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11.7-slim-bookworm
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
COPY ./requirements.txt /app
|
6 |
+
|
7 |
+
RUN pip install -r requirements.txt
|
8 |
+
|
9 |
+
COPY . /app
|
10 |
+
|
11 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]
|
README.md
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div style="width:100%;display:flex;flex-direction:column;justify-content:center;align-items:center">
|
2 |
+
<img style="width:80px"
|
3 |
+
src="https://seeklogo.com/images/G/google-developers-logo-F8BF3155AC-seeklogo.com.png" alt="logo"/>
|
4 |
+
</div>
|
5 |
+
<h3 style="text-align:center">Google Developer Student Clubs - FPT University Da Nang</h3>
|
6 |
+
|
7 |
+
# AI Services
|
8 |
+
|
9 |
+

|
10 |
+

|
11 |
+
|
12 |
+
## ✨ Available Services
|
13 |
+
|
14 |
+
## ⏩ Forwarding Server
|
15 |
+
|
16 |
+
The forwarding server help adding children server for processing. The parent server acts as a controller for load balancing.
|
17 |
+
|
18 |
+
```bash
|
19 |
+
# Specifying forwarding server by query params `fw`
|
20 |
+
curl --location 'https://{api-endpoint}/?fw=0' --request 'POST'
|
21 |
+
|
22 |
+
# Auto forwarding server. The parent server will controll loading balance among registered servers.
|
23 |
+
curl --location 'https://{api-endpoint}/?fw=auto' --request 'POST'
|
24 |
+
```
|
25 |
+
|
26 |
+
- Get all registered forwarding servers.
|
27 |
+
|
28 |
+
```bash
|
29 |
+
curl --location 'https://{domain}/service/fw/' --request 'GET'
|
30 |
+
```
|
31 |
+
|
32 |
+
- Register forwarding server.
|
33 |
+
|
34 |
+
```bash
|
35 |
+
curl --location 'https://{domain}/service/fw/' --request 'POST' --data '{"url":"{children-base-domain}"}'
|
36 |
+
```
|
37 |
+
|
38 |
+
- Delete forwarding server.
|
39 |
+
|
40 |
+
```bash
|
41 |
+
curl --location 'https://{domain}/service/fw/' --request 'DELETE' --data '{"index":0}'
|
42 |
+
```
|
43 |
+
|
44 |
+
### Children Server Endpoint Request & Response
|
45 |
+
|
46 |
+
- `/api/rembg/`
|
47 |
+
|
48 |
+
```json
|
49 |
+
// Request. Form-data
|
50 |
+
{
|
51 |
+
"image": "<image-data>"
|
52 |
+
}
|
53 |
+
|
54 |
+
// Response. JSON
|
55 |
+
{
|
56 |
+
"data": {
|
57 |
+
"image": "<static-access-url>"
|
58 |
+
}
|
59 |
+
}
|
60 |
+
```
|
61 |
+
|
62 |
+
## 😊 Contributors
|
63 |
+
|
64 |
+
- GDSC-FPTU [[gdsc-fptu](https://github.com/gdsc-fptu)]
|
65 |
+
- Đoàn Quang Minh [[Ming-doan](https://github.com/Ming-doan)]
|
apps/apis/__init__.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from .rembg import router as rembg_router
|
3 |
+
from .img2text import router as img2text_router
|
4 |
+
from .chats import router as chats_router
|
5 |
+
from .fd import router as fd_router
|
6 |
+
|
7 |
+
router = APIRouter(prefix='/api')
|
8 |
+
|
9 |
+
# Register routes
|
10 |
+
router.include_router(rembg_router)
|
11 |
+
router.include_router(img2text_router)
|
12 |
+
router.include_router(chats_router)
|
13 |
+
router.include_router(fd_router)
|
apps/apis/chats/__init__.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from fastapi.requests import Request
|
4 |
+
|
5 |
+
router = APIRouter(prefix='/chats')
|
6 |
+
router_base_configs = {
|
7 |
+
"tags": ["chats"],
|
8 |
+
"response_class": JSONResponse
|
9 |
+
}
|
10 |
+
|
11 |
+
|
12 |
+
# Message
|
13 |
+
@router.post("/:conversation_id", **router_base_configs)
|
14 |
+
def message():
|
15 |
+
return {"message": "Hello World"}
|
16 |
+
|
17 |
+
|
18 |
+
# Get conversations
|
19 |
+
@router.get("/get", **router_base_configs)
|
20 |
+
def get_conversations():
|
21 |
+
return {"conversations": "Hello World"}
|
22 |
+
|
23 |
+
|
24 |
+
# Create conversation
|
25 |
+
@router.post("/create", **router_base_configs)
|
26 |
+
def create_conversation():
|
27 |
+
return {"conversation_id": "Hello World"}
|
28 |
+
|
29 |
+
|
30 |
+
# Delete conversation
|
31 |
+
@router.delete("/delete", **router_base_configs)
|
32 |
+
def delete_conversation():
|
33 |
+
return {"conversation": "Hello World"}
|
apps/apis/fd/__init__.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
|
4 |
+
router = APIRouter(prefix='/fd')
|
5 |
+
router_base_configs = {
|
6 |
+
"tags": ["fd"],
|
7 |
+
"response_class": JSONResponse
|
8 |
+
}
|
9 |
+
|
10 |
+
|
11 |
+
# Face detection
|
12 |
+
@router.post("/", **router_base_configs)
|
13 |
+
def faces_detection():
|
14 |
+
return {"faces": "Hello World"}
|
apps/apis/img2text/__init__.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
|
4 |
+
router = APIRouter(prefix='/img2text')
|
5 |
+
router_base_configs = {
|
6 |
+
"tags": ["img2text"],
|
7 |
+
"response_class": JSONResponse
|
8 |
+
}
|
9 |
+
|
10 |
+
|
11 |
+
# Image to text
|
12 |
+
@router.post("/", **router_base_configs)
|
13 |
+
def image_to_text():
|
14 |
+
return {"caption": "Hello World"}
|
apps/apis/rembg/__init__.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, HTTPException, Depends
|
2 |
+
from fastapi.responses import JSONResponse, StreamingResponse
|
3 |
+
from apps.services.foward.fw_middleware import forward_middleware, forward_request
|
4 |
+
from .models.rembg_model import RembgModel
|
5 |
+
from .controllers.rembg_controller import rembg_controller
|
6 |
+
from utils.local_storage import save_to_local, remove_from_local_with_expire
|
7 |
+
|
8 |
+
router = APIRouter(prefix='/rembg')
|
9 |
+
router_base_configs = {
|
10 |
+
"tags": ["rembg"],
|
11 |
+
"response_class": JSONResponse,
|
12 |
+
}
|
13 |
+
|
14 |
+
|
15 |
+
# Remove background
|
16 |
+
@router.post("/", **router_base_configs)
|
17 |
+
async def remove_background(
|
18 |
+
image: RembgModel.image = RembgModel.image_default,
|
19 |
+
stream: RembgModel.stream = RembgModel.stream_default,
|
20 |
+
expire: RembgModel.expire = RembgModel.expire_default,
|
21 |
+
fw_index: RembgModel.fw_index = Depends(forward_middleware)
|
22 |
+
):
|
23 |
+
# Forward request
|
24 |
+
fw_data = {
|
25 |
+
"files": {"image": image.file}
|
26 |
+
}
|
27 |
+
fw_response = forward_request(fw_index, fw_data, '/api/rembg/')
|
28 |
+
if fw_response is not None:
|
29 |
+
return JSONResponse({
|
30 |
+
"message": "Image is processed successfully",
|
31 |
+
"access_link": fw_response["data"]['image']
|
32 |
+
})
|
33 |
+
|
34 |
+
# Check if image is None
|
35 |
+
if image is None:
|
36 |
+
raise HTTPException(status_code=400, detail="Image is required")
|
37 |
+
# Check if image is empty
|
38 |
+
if image.filename == '':
|
39 |
+
raise HTTPException(status_code=400, detail="Image is empty")
|
40 |
+
# Read image
|
41 |
+
image_bytes = await image.read()
|
42 |
+
# Process image
|
43 |
+
processed_image = await rembg_controller(image_bytes)
|
44 |
+
# If stream is True, return StreamingResponse
|
45 |
+
if RembgModel.stream_parser(stream):
|
46 |
+
return StreamingResponse(processed_image, media_type="application/octet-stream", headers={"Content-Disposition": f"attachment;filename={image.filename}"})
|
47 |
+
# If stream is False, save locally and return access link
|
48 |
+
else:
|
49 |
+
# Save image
|
50 |
+
local_path = save_to_local(processed_image.read(), image.filename)
|
51 |
+
# Automatic delete file
|
52 |
+
remove_from_local_with_expire(local_path, expire)
|
53 |
+
# Return access link
|
54 |
+
return JSONResponse({
|
55 |
+
"message": "Image is processed successfully",
|
56 |
+
"access_link": local_path
|
57 |
+
})
|
apps/apis/rembg/controllers/rembg_controller.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import rembg
|
2 |
+
import io
|
3 |
+
|
4 |
+
|
5 |
+
async def rembg_controller(image: bytes):
|
6 |
+
# Remove background
|
7 |
+
image = rembg.remove(image)
|
8 |
+
# Return image
|
9 |
+
return io.BytesIO(image)
|
apps/apis/rembg/models/rembg_model.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import UploadFile, File, Form
|
2 |
+
|
3 |
+
|
4 |
+
class RembgModel:
|
5 |
+
image = UploadFile | None
|
6 |
+
image_default = File(
|
7 |
+
None, description="Image to remove background", example="image.jpg")
|
8 |
+
stream = str | None
|
9 |
+
stream_default = Form(
|
10 |
+
None, description="Is return a streaming or access json", example="false")
|
11 |
+
expire = int | None
|
12 |
+
expire_default = Form(
|
13 |
+
None, description="Expire time of access link", example=3600)
|
14 |
+
fw_index = int | None
|
15 |
+
fw_index_default = Form(
|
16 |
+
None, description="Forward destination index", example=0)
|
17 |
+
|
18 |
+
def stream_parser(x): return False if x == 'false' or x == 0 else True
|
apps/create_app.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from fastapi.staticfiles import StaticFiles
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from utils.metadata import DOC_SWAGGER
|
5 |
+
|
6 |
+
|
7 |
+
def create_app():
|
8 |
+
app = FastAPI(**DOC_SWAGGER)
|
9 |
+
app.add_middleware(
|
10 |
+
CORSMiddleware,
|
11 |
+
allow_origins=['*'],
|
12 |
+
allow_credentials=True,
|
13 |
+
allow_methods=['*'],
|
14 |
+
allow_headers=['*'],
|
15 |
+
)
|
16 |
+
app.mount("/static", StaticFiles(directory="temp"), name="static")
|
17 |
+
return app
|
apps/services/__init__.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from .foward import router as foward_router
|
3 |
+
|
4 |
+
router = APIRouter(prefix='/service')
|
5 |
+
|
6 |
+
# Register routes
|
7 |
+
router.include_router(foward_router)
|
apps/services/foward/__init__.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from .models.fw_model import CreateFwDestinationModel, DeleteFwDestinationModel
|
4 |
+
from .fw_storage import get_fw_destinations, append_fw_destination, delete_fw_destination
|
5 |
+
|
6 |
+
|
7 |
+
router = APIRouter(prefix='/fw')
|
8 |
+
router_base_configs = {
|
9 |
+
"tags": ["fw"],
|
10 |
+
"response_class": JSONResponse
|
11 |
+
}
|
12 |
+
|
13 |
+
|
14 |
+
# Create foward destination
|
15 |
+
@router.post("/", **router_base_configs)
|
16 |
+
def create_forward_destination(data: CreateFwDestinationModel):
|
17 |
+
index = append_fw_destination(data.url)
|
18 |
+
return {"fw_index": index}
|
19 |
+
|
20 |
+
|
21 |
+
# Delete foward destination
|
22 |
+
@router.delete("/", **router_base_configs)
|
23 |
+
def delete_forward_destination(data: DeleteFwDestinationModel):
|
24 |
+
delete_fw_destination(data.index)
|
25 |
+
return {"fw_index": data.index}
|
26 |
+
|
27 |
+
|
28 |
+
# Get foward destinations
|
29 |
+
@router.get("/", **router_base_configs)
|
30 |
+
def get_forward_destinations():
|
31 |
+
return {"fw": get_fw_destinations()}
|
apps/services/foward/fw_middleware.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, Literal
|
2 |
+
import requests
|
3 |
+
from fastapi import HTTPException
|
4 |
+
from .fw_storage import get_fw_destinations, update_fw_destination
|
5 |
+
|
6 |
+
|
7 |
+
async def verify_fw_resources(url: str) -> bool:
|
8 |
+
try:
|
9 |
+
response = requests.get(url + '/health')
|
10 |
+
if response.status_code == 200:
|
11 |
+
return True
|
12 |
+
else:
|
13 |
+
return False
|
14 |
+
except Exception:
|
15 |
+
return False
|
16 |
+
|
17 |
+
|
18 |
+
async def forward_middleware(fw: str = None):
|
19 |
+
# Verify forward destination index
|
20 |
+
try:
|
21 |
+
if fw != 'auto':
|
22 |
+
fw = int(fw)
|
23 |
+
except Exception:
|
24 |
+
raise HTTPException(
|
25 |
+
status_code=400, detail="Invalid forward destination")
|
26 |
+
|
27 |
+
# If forward destination is auto, get the destination with the least tasks
|
28 |
+
fw_destinations = get_fw_destinations()
|
29 |
+
if fw == 'auto':
|
30 |
+
if len(fw_destinations) != 0:
|
31 |
+
# Sort resources by tasks
|
32 |
+
fw_destinations.sort(key=lambda x: x['tasks'])
|
33 |
+
# Verify forward destination resources
|
34 |
+
fw = 0
|
35 |
+
for i in range(0, len(fw_destinations)):
|
36 |
+
is_resource_available = await verify_fw_resources(fw_destinations[fw]['url'])
|
37 |
+
if is_resource_available:
|
38 |
+
fw = i
|
39 |
+
break
|
40 |
+
else:
|
41 |
+
fw = None
|
42 |
+
# Update forward destination tasks
|
43 |
+
if fw is not None:
|
44 |
+
update_fw_destination(fw, fw_destinations[fw]['tasks'] + 1)
|
45 |
+
else:
|
46 |
+
fw = None
|
47 |
+
# If forward destination is not auto, update forward destination tasks
|
48 |
+
else:
|
49 |
+
update_fw_destination(fw, fw_destinations[fw]['tasks'] + 1)
|
50 |
+
|
51 |
+
return fw
|
52 |
+
|
53 |
+
|
54 |
+
def forward_request(fw_index: int, data: Any, endpoint: str = '', method: Literal['GET', 'POST', 'PUT', 'DELETE'] = 'POST'):
|
55 |
+
# Get forward destination url
|
56 |
+
fw_destinations = get_fw_destinations()
|
57 |
+
fw_url = fw_destinations[fw_index]['url'] + endpoint
|
58 |
+
# Call request
|
59 |
+
try:
|
60 |
+
if method == 'GET':
|
61 |
+
response = requests.get(fw_url)
|
62 |
+
elif method == 'POST':
|
63 |
+
response = requests.post(fw_url, **data)
|
64 |
+
elif method == 'PUT':
|
65 |
+
response = requests.put(fw_url, **data)
|
66 |
+
elif method == 'DELETE':
|
67 |
+
response = requests.delete(fw_url, **data)
|
68 |
+
else:
|
69 |
+
raise HTTPException(
|
70 |
+
status_code=400, detail="Invalid request method on forwarding request")
|
71 |
+
except Exception as e:
|
72 |
+
print("Forwarding Error: ", e)
|
73 |
+
response = None
|
74 |
+
# Update forward destination tasks
|
75 |
+
update_fw_destination(fw_index, fw_destinations[fw_index]['tasks'] - 1)
|
76 |
+
# Return response
|
77 |
+
return response.json() if response is not None else None
|
apps/services/foward/fw_storage.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from utils.local_storage import read_from_local, save_to_local
|
2 |
+
|
3 |
+
|
4 |
+
FOWARD_JSON_FILE = 'forward.json'
|
5 |
+
|
6 |
+
|
7 |
+
def get_fw_destinations() -> list:
|
8 |
+
destinations = read_from_local(FOWARD_JSON_FILE)
|
9 |
+
if destinations is None:
|
10 |
+
return []
|
11 |
+
return destinations
|
12 |
+
|
13 |
+
|
14 |
+
def append_fw_destination(url: str):
|
15 |
+
# Remove the last / if exist
|
16 |
+
if url[-1] == '/':
|
17 |
+
url = url[:-1]
|
18 |
+
# Get forward destinations
|
19 |
+
forward_destinations = get_fw_destinations()
|
20 |
+
# Append new destination
|
21 |
+
forward_destinations.append({
|
22 |
+
"tasks": 0,
|
23 |
+
"url": url
|
24 |
+
})
|
25 |
+
# Save to local
|
26 |
+
save_to_local(forward_destinations, FOWARD_JSON_FILE, False)
|
27 |
+
# Return index
|
28 |
+
return len(forward_destinations) - 1
|
29 |
+
|
30 |
+
|
31 |
+
def update_fw_destination(index: int, tasks: int):
|
32 |
+
# Get forward destinations
|
33 |
+
forward_destinations = get_fw_destinations()
|
34 |
+
# Update destination
|
35 |
+
forward_destinations[index]['tasks'] = tasks
|
36 |
+
# Save to local
|
37 |
+
save_to_local(forward_destinations, FOWARD_JSON_FILE, False)
|
38 |
+
|
39 |
+
|
40 |
+
def delete_fw_destination(index: int):
|
41 |
+
# Get forward destinations
|
42 |
+
forward_destinations = get_fw_destinations()
|
43 |
+
# Delete destination
|
44 |
+
del forward_destinations[index]
|
45 |
+
# Save to local
|
46 |
+
save_to_local(forward_destinations, FOWARD_JSON_FILE, False)
|
apps/services/foward/models/fw_model.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
|
4 |
+
class CreateFwDestinationModel(BaseModel):
|
5 |
+
url: str = None
|
6 |
+
|
7 |
+
|
8 |
+
class DeleteFwDestinationModel(BaseModel):
|
9 |
+
index: int = None
|
docker-compose.yaml
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: '3.8'
|
2 |
+
|
3 |
+
services:
|
4 |
+
api:
|
5 |
+
build: .
|
6 |
+
container_name: "gdsc-ai-service-container"
|
7 |
+
ports:
|
8 |
+
- "8000:80"
|
9 |
+
volumes:
|
10 |
+
- .:/app
|
11 |
+
- /app/env
|
12 |
+
env_file:
|
13 |
+
- .env
|
main.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Google Developer Student Clubs - FPT University Da Nang
|
2 |
+
import os
|
3 |
+
import uvicorn
|
4 |
+
from apps.create_app import create_app
|
5 |
+
from apps.services.foward.fw_middleware import forward_middleware
|
6 |
+
# Import routers
|
7 |
+
from apps.apis import router as api_router
|
8 |
+
from apps.services import router as service_router
|
9 |
+
|
10 |
+
app = create_app()
|
11 |
+
|
12 |
+
# Register apps routes
|
13 |
+
app.include_router(api_router)
|
14 |
+
app.include_router(service_router)
|
15 |
+
|
16 |
+
# Config uvicorn
|
17 |
+
if os.environ.get("ENV", "production") == 'development':
|
18 |
+
uvicorn.run(app, port=8000)
|
requirements.txt
ADDED
Binary file (2.18 kB). View file
|
|
temp/.gitkeep
ADDED
File without changes
|
utils/constants.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
APP_DOMAIN = "http://localhost:8000"
|
4 |
+
|
5 |
+
TEMP_DIR = os.path.join(os.getcwd(), 'temp')
|
6 |
+
|
7 |
+
DOC_TEMPLATE = f'''
|
8 |
+
<!DOCTYPE html>
|
9 |
+
<html>
|
10 |
+
<head>
|
11 |
+
<meta charset="utf-8">
|
12 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
13 |
+
<title>GDSC AI Service</title>
|
14 |
+
</head>
|
15 |
+
<body>
|
16 |
+
<img src="https://seeklogo.com/images/G/google-developers-logo-F8BF3155AC-seeklogo.com.png" alt="logo" width="100">
|
17 |
+
<h1>GDSC AI Back-end Service</h1>
|
18 |
+
<span>Served URL: </span>
|
19 |
+
<a href="{APP_DOMAIN}">{APP_DOMAIN}</a>
|
20 |
+
<ul>
|
21 |
+
<li>Remove Background Service [POST]</li>
|
22 |
+
<code>{APP_DOMAIN}/api/rembg</code>
|
23 |
+
</ul>
|
24 |
+
<ul>
|
25 |
+
<li>Image to Text [POST]</li>
|
26 |
+
<code>{APP_DOMAIN}/api/img2text</code>
|
27 |
+
</ul>
|
28 |
+
<ul>
|
29 |
+
<li>Chats [POST]</li>
|
30 |
+
<code>{APP_DOMAIN}/api/chats</code>
|
31 |
+
</ul>
|
32 |
+
<ul>
|
33 |
+
<li>Face Detection [POST]</li>
|
34 |
+
<code>{APP_DOMAIN}/api/fd</code>
|
35 |
+
</ul>
|
36 |
+
</body>
|
37 |
+
</html>
|
38 |
+
'''
|
utils/local_storage.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
import os
|
3 |
+
import json
|
4 |
+
from threading import Timer
|
5 |
+
from .constants import TEMP_DIR, APP_DOMAIN
|
6 |
+
|
7 |
+
|
8 |
+
def parse_filename(filename: str):
|
9 |
+
filename = filename.replace(' ', '_')
|
10 |
+
filename = filename.replace('(', '')
|
11 |
+
filename = filename.replace(')', '')
|
12 |
+
filename = filename.replace('[', '')
|
13 |
+
filename = filename.replace(']', '')
|
14 |
+
filename = filename.replace('{', '')
|
15 |
+
filename = filename.replace('}', '')
|
16 |
+
filename = filename.replace('<', '')
|
17 |
+
filename = filename.replace('>', '')
|
18 |
+
filename = filename.replace(';', '')
|
19 |
+
filename = filename.replace(':', '')
|
20 |
+
filename = filename.replace('"', '')
|
21 |
+
filename = filename.replace("'", '')
|
22 |
+
filename = filename.replace('\\', '')
|
23 |
+
filename = filename.replace('/', '')
|
24 |
+
filename = filename.replace('|', '')
|
25 |
+
filename = filename.replace('?', '')
|
26 |
+
filename = filename.replace('*', '')
|
27 |
+
# If file is exist, add number to filename
|
28 |
+
if os.path.isfile(os.path.join(TEMP_DIR, filename)):
|
29 |
+
filename = filename.split('.')
|
30 |
+
filename = f"{filename[0]}_1.{filename[1]}"
|
31 |
+
return filename
|
32 |
+
|
33 |
+
|
34 |
+
def save_to_local(file: bytes | Any, filename: str, is_parse_filename: bool = True):
|
35 |
+
# Parse filename
|
36 |
+
if is_parse_filename:
|
37 |
+
filename = parse_filename(filename)
|
38 |
+
# Get type of file
|
39 |
+
file_extension = filename.split('.')[-1]
|
40 |
+
# Get write mode
|
41 |
+
if file_extension == 'json':
|
42 |
+
mode = 'w'
|
43 |
+
else:
|
44 |
+
mode = 'wb'
|
45 |
+
# Save file
|
46 |
+
with open(os.path.join(TEMP_DIR, filename), mode) as f:
|
47 |
+
if file_extension == 'json':
|
48 |
+
json.dump(file, f)
|
49 |
+
else:
|
50 |
+
f.write(file)
|
51 |
+
# Return access link
|
52 |
+
return f"{APP_DOMAIN}/static/{filename}"
|
53 |
+
|
54 |
+
|
55 |
+
def read_from_local(filename: str):
|
56 |
+
# Get type of file
|
57 |
+
file_extension = filename.split('.')[-1]
|
58 |
+
# Get read mode
|
59 |
+
if file_extension == 'json':
|
60 |
+
mode = 'r'
|
61 |
+
else:
|
62 |
+
mode = 'rb'
|
63 |
+
# If file is exist, return file
|
64 |
+
if os.path.isfile(os.path.join(TEMP_DIR, filename)):
|
65 |
+
with open(os.path.join(TEMP_DIR, filename), mode) as f:
|
66 |
+
if file_extension == 'json':
|
67 |
+
return json.load(f)
|
68 |
+
return f.read()
|
69 |
+
|
70 |
+
|
71 |
+
def remove_from_local(filename: str):
|
72 |
+
# If file is exist, add number to filename
|
73 |
+
if os.path.isfile(os.path.join(TEMP_DIR, filename)):
|
74 |
+
os.remove(os.path.join(TEMP_DIR, filename))
|
75 |
+
|
76 |
+
|
77 |
+
def remove_from_local_with_expire(filename: str, expire: int):
|
78 |
+
# Remove file after expire time
|
79 |
+
if expire is not None and expire > 0:
|
80 |
+
t = Timer(expire, remove_from_local, args=[filename])
|
81 |
+
t.start()
|
utils/metadata.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
DOC_SWAGGER = {
|
2 |
+
"title": "GDSC AI Service",
|
3 |
+
"description": "GDSC AI Back-end Service",
|
4 |
+
"version": "0.0.1",
|
5 |
+
"openapi": "3.0.2",
|
6 |
+
"info": {
|
7 |
+
"title": "GDSC AI Service",
|
8 |
+
"description": "GDSC AI Back-end Service",
|
9 |
+
"version": "0.0.1"
|
10 |
+
},
|
11 |
+
"docs_url": "/",
|
12 |
+
"openapi_tags": [
|
13 |
+
{
|
14 |
+
"name": "rembg",
|
15 |
+
"description": "Remove background from image"
|
16 |
+
},
|
17 |
+
{
|
18 |
+
"name": "img2text",
|
19 |
+
"description": "Extract text from image"
|
20 |
+
},
|
21 |
+
{
|
22 |
+
"name": "chats",
|
23 |
+
"description": "Chat with AI"
|
24 |
+
},
|
25 |
+
{
|
26 |
+
"name": "fd",
|
27 |
+
"description": "Detect faces from image"
|
28 |
+
},
|
29 |
+
{
|
30 |
+
"name": "fw",
|
31 |
+
"description": "Foward request to other services"
|
32 |
+
}
|
33 |
+
]
|
34 |
+
}
|