add waas modal部署

This commit is contained in:
kyj@bowong.ai 2025-07-31 18:59:50 +08:00
parent c69d3cc326
commit f0ee6e21d7
7 changed files with 70 additions and 24 deletions

View File

@ -1,10 +1,10 @@
# ComfyUI服务器地址 # ComfyUI服务器地址
COMFYUI_URL="ws://127.0.0.1:8188/ws" COMFYUI_URL="ws://bowongai--local-ui-4.modal.run/ws"
COMFYUI_HTTP_URL="http://127.0.0.1:8188" COMFYUI_HTTP_URL="https://bowongai--local-ui-4.modal.run"
# 绝对路径!例如: /home/user/ComfyUI/input # 绝对路径!例如: /home/user/ComfyUI/input
COMFYUI_INPUT_DIR="F:\ComfyUI\input" COMFYUI_INPUT_DIR="/db/input"
# 绝对路径!例如: /home/user/ComfyUI/output # 绝对路径!例如: /home/user/ComfyUI/output
COMFYUI_OUTPUT_DIR="F:\ComfyUI\output" COMFYUI_OUTPUT_DIR="/db/output"
# AWS S3 配置 # AWS S3 配置
S3_BUCKET_NAME="modal-media-cache" S3_BUCKET_NAME="modal-media-cache"

29
modal_deploy.py Normal file
View File

@ -0,0 +1,29 @@
import modal
from dotenv import dotenv_values
from workflow_service.main import web_app
fastapi_image = (
modal.Image
.debian_slim(python_version="3.11")
.pip_install_from_requirements("./workflow_service/requirements.txt")
.env(dotenv_values(".env"))
.add_local_python_source("workflow_service")
)
app = modal.App(image=fastapi_image, name="waas-demo")
vol = modal.Volume.from_name("comfy_model", environment_name="dev", create_if_missing=True)
with fastapi_image.imports():
@app.function(
cpu=(0.125, 8),
memory=(128, 4096),
scaledown_window=1200,
volumes={
"/db": vol
}
)
@modal.concurrent(max_inputs=100)
@modal.asgi_app()
def fastapi_webapp():
return web_app

View File

@ -3,7 +3,8 @@ import json
import uuid import uuid
import aiohttp import aiohttp
import random import random
from config import settings from workflow_service.config import Settings
settings = Settings()
async def queue_prompt(prompt: dict, client_id: str) -> str: async def queue_prompt(prompt: dict, client_id: str) -> str:

View File

@ -1,7 +1,8 @@
from dotenv import dotenv_values
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8') model_config = SettingsConfigDict(env_file='./.env', env_file_encoding='utf-8')
COMFYUI_URL: str = "ws://127.0.0.1:8188/ws" COMFYUI_URL: str = "ws://127.0.0.1:8188/ws"
COMFYUI_HTTP_URL: str = "http://127.0.0.1:8188" COMFYUI_HTTP_URL: str = "http://127.0.0.1:8188"
COMFYUI_INPUT_DIR: str COMFYUI_INPUT_DIR: str
@ -11,4 +12,4 @@ class Settings(BaseSettings):
AWS_SECRET_ACCESS_KEY: str AWS_SECRET_ACCESS_KEY: str
AWS_REGION_NAME: str AWS_REGION_NAME: str
settings = Settings() # settings = Settings()

View File

@ -2,7 +2,7 @@ import aiosqlite
import json import json
import re import re
DATABASE_FILE = "workflows_service.sqlite" DATABASE_FILE = "/db/workflows_service.sqlite"
async def init_db(): async def init_db():
async with aiosqlite.connect(DATABASE_FILE) as db: async with aiosqlite.connect(DATABASE_FILE) as db:

View File

@ -1,14 +1,24 @@
import aiohttp
from workflow_service import comfyui_client
from workflow_service import database
import json
import os
from workflow_service import s3_client
import uuid
from workflow_service import workflow_parser
from typing import Optional, List, Dict, Any, Set
import uvicorn import uvicorn
from fastapi import FastAPI, Request, HTTPException, Path from fastapi import FastAPI, Request, HTTPException, Path
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from typing import Optional, List, Dict, Any, Set
import json, uuid, os, shutil, aiohttp, database, workflow_parser, comfyui_client, s3_client
from config import settings
app = FastAPI(title="ComfyUI Workflow Service & Management API") from workflow_service.config import Settings
settings = Settings()
web_app = FastAPI(title="ComfyUI Workflow Service & Management API")
@app.on_event("startup") @web_app.on_event("startup")
async def startup_event(): await database.init_db(); os.makedirs(settings.COMFYUI_INPUT_DIR, async def startup_event(): await database.init_db(); os.makedirs(settings.COMFYUI_INPUT_DIR,
exist_ok=True); os.makedirs( exist_ok=True); os.makedirs(
settings.COMFYUI_OUTPUT_DIR, exist_ok=True) settings.COMFYUI_OUTPUT_DIR, exist_ok=True)
@ -19,17 +29,20 @@ async def startup_event(): await database.init_db(); os.makedirs(settings.COMFYU
BASE_MANAGEMENT_PATH = "/api/workflow" BASE_MANAGEMENT_PATH = "/api/workflow"
@app.post(BASE_MANAGEMENT_PATH, status_code=200) @web_app.post(BASE_MANAGEMENT_PATH, status_code=200)
async def publish_workflow_endpoint(request: Request): async def publish_workflow_endpoint(request: Request):
try: try:
data = await request.json(); name, wf_json = data.get("name"), data.get( data = await request.json();
"workflow"); await database.save_workflow(name, json.dumps(wf_json)); return JSONResponse( name, wf_json = data.get("name"), data.get(
"workflow");
await database.save_workflow(name, json.dumps(wf_json));
return JSONResponse(
content={"status": "success", "message": f"Workflow '{name}' published."}, status_code=200) content={"status": "success", "message": f"Workflow '{name}' published."}, status_code=200)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save workflow: {e}") raise HTTPException(status_code=500, detail=f"Failed to save workflow: {e}")
@app.get(BASE_MANAGEMENT_PATH, response_model=List[dict]) @web_app.get(BASE_MANAGEMENT_PATH, response_model=List[dict])
async def get_all_workflows_endpoint(): async def get_all_workflows_endpoint():
try: try:
return await database.get_all_workflows() return await database.get_all_workflows()
@ -37,7 +50,7 @@ async def get_all_workflows_endpoint():
raise HTTPException(status_code=500, detail=f"Failed to get workflows: {e}") raise HTTPException(status_code=500, detail=f"Failed to get workflows: {e}")
@app.delete(f"{BASE_MANAGEMENT_PATH}/{{workflow_name:path}}") @web_app.delete(f"{BASE_MANAGEMENT_PATH}/{{workflow_name:path}}")
async def delete_workflow_endpoint(workflow_name: str = Path(..., title="...")): async def delete_workflow_endpoint(workflow_name: str = Path(..., title="...")):
try: try:
success = await database.delete_workflow(workflow_name); success = await database.delete_workflow(workflow_name);
@ -75,7 +88,7 @@ async def handle_file_upload(file_path: str, base_name: str) -> str:
return await s3_client.upload_file_to_s3(file_path, settings.S3_BUCKET_NAME, s3_object_name) return await s3_client.upload_file_to_s3(file_path, settings.S3_BUCKET_NAME, s3_object_name)
@app.post("/api/run/{base_name}") @web_app.post("/api/run/{base_name}")
async def execute_workflow_endpoint(base_name: str, request_data_raw: Dict[str, Any], version: Optional[str] = None): async def execute_workflow_endpoint(base_name: str, request_data_raw: Dict[str, Any], version: Optional[str] = None):
cleanup_paths = [] cleanup_paths = []
try: try:
@ -102,7 +115,8 @@ async def execute_workflow_endpoint(base_name: str, request_data_raw: Dict[str,
save_path = os.path.join(settings.COMFYUI_INPUT_DIR, filename) save_path = os.path.join(settings.COMFYUI_INPUT_DIR, filename)
try: try:
await download_file_from_url(session, image_url, save_path); await download_file_from_url(session, image_url, save_path);
request_data[param_name] = filename; cleanup_paths.append(save_path) request_data[param_name] = filename;
cleanup_paths.append(save_path)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, raise HTTPException(status_code=500,
detail=f"Failed to download file for '{param_name}' from {image_url}. Error: {e}") detail=f"Failed to download file for '{param_name}' from {image_url}. Error: {e}")
@ -187,7 +201,7 @@ async def execute_workflow_endpoint(base_name: str, request_data_raw: Dict[str,
# --- Section 3: 工作流元数据/规范API (无改动) --- # --- Section 3: 工作流元数据/规范API (无改动) ---
# ... (此部分代码与上一版完全相同) ... # ... (此部分代码与上一版完全相同) ...
@app.get("/api/spec/{base_name}") @web_app.get("/api/spec/{base_name}")
async def get_workflow_spec_endpoint(base_name: str, version: Optional[str] = None): async def get_workflow_spec_endpoint(base_name: str, version: Optional[str] = None):
# ... # ...
if version: if version:
@ -204,9 +218,9 @@ async def get_workflow_spec_endpoint(base_name: str, version: Optional[str] = No
raise HTTPException(status_code=500, detail=f"Failed to parse workflow specification: {e}") raise HTTPException(status_code=500, detail=f"Failed to parse workflow specification: {e}")
@app.get("/") @web_app.get("/")
def read_root(): return {"message": "Welcome to the ComfyUI Workflow Service API!"} def read_root(): return {"message": "Welcome to the ComfyUI Workflow Service API!"}
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=18000) uvicorn.run(web_app, host="127.0.0.1", port=18000)

View File

@ -1,6 +1,7 @@
import boto3 import boto3
from config import settings from workflow_service.config import Settings
import asyncio import asyncio
settings = Settings()
s3_client = boto3.client('s3', aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, region_name=settings.AWS_REGION_NAME) s3_client = boto3.client('s3', aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, region_name=settings.AWS_REGION_NAME)