diff --git a/main.py b/main.py index fbaa6d8..7f9f894 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ from pretor.core.individual.supervisory_node.supervisory_node import Supervisory from pretor.core.individual.consciousness_node.consciousness_node import ConsciousnessNode from pretor.core.individual.control_node.control_node import ControlNode from pretor.core.workflow.workflow_runner import WorkflowRunningEngine -from pretor.core.api import PretorGateway +from pretor.api import PretorGateway from ray import serve import os import secrets diff --git a/pretor/api/__init__.py b/pretor/api/__init__.py index 5fa7362..1b47ece 100644 --- a/pretor/api/__init__.py +++ b/pretor/api/__init__.py @@ -12,3 +12,118 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os +from typing import Dict + +from fastapi import FastAPI, WebSocket, Request +from fastapi.responses import FileResponse, JSONResponse +from fastapi.staticfiles import StaticFiles +from ray import serve + +from .agent import agent_router +from .auth import auth_router +from .cluster import cluster_router +from .platform.frontend import client_router +from .provider import provider_router +from .resource import resource_router +from .workflow import workflow_router +from pretor.utils.error import ( + DemandError, ModelNotExistError, UserError, + UserNotExistError, UserPasswordError, ProviderError, + ProviderNotExistError, WorkflowError, WorkflowExit +) + +app = FastAPI() + +app.include_router(client_router) # 客户端路径 +app.include_router(auth_router) # 用户路径 +app.include_router(provider_router) # 供应商路径 +app.include_router(resource_router) # 资源路径 +app.include_router(cluster_router) # 集群信息路径 +app.include_router(agent_router) # agent路径 +app.include_router(workflow_router) # workflow路径 + +@app.exception_handler(UserNotExistError) +async def user_not_exist_handler(request: Request, exc: UserNotExistError): + return JSONResponse(status_code=404, content={"message": "用户不存在"}) + + +@app.exception_handler(UserPasswordError) +async def user_password_handler(request: Request, exc: UserPasswordError): + return JSONResponse(status_code=401, content={"message": "密码错误"}) + + +@app.exception_handler(UserError) +async def user_error_handler(request: Request, exc: UserError): + return JSONResponse(status_code=400, content={"message": "用户相关错误"}) + + +@app.exception_handler(ProviderNotExistError) +async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError): + return JSONResponse(status_code=404, content={"message": "服务提供商不存在"}) + + +@app.exception_handler(ProviderError) +async def provider_error_handler(request: Request, exc: ProviderError): + return JSONResponse(status_code=400, content={"message": "服务提供商错误"}) + + +@app.exception_handler(ModelNotExistError) +async def model_not_exist_handler(request: Request, exc: ModelNotExistError): + return JSONResponse(status_code=404, content={"message": "模型不存在"}) + + +@app.exception_handler(DemandError) +async def demand_error_handler(request: Request, exc: DemandError): + return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"}) + + +@app.exception_handler(WorkflowExit) +async def workflow_exit_handler(request: Request, exc: WorkflowExit): + return JSONResponse(status_code=400, content={"message": "工作流已退出"}) + + +@app.exception_handler(WorkflowError) +async def workflow_error_handler(request: Request, exc: WorkflowError): + return JSONResponse(status_code=500, content={"message": "工作流执行错误"}) + +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +frontend_dir = os.path.join(base_dir, "frontend", "dist") + +if os.path.exists(frontend_dir): + app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets") + + + @app.get("/favicon.svg", include_in_schema=False) + async def serve_favicon(): + return FileResponse(os.path.join(frontend_dir, "favicon.svg")) + + + @app.get("/icons.svg", include_in_schema=False) + async def serve_icons(): + return FileResponse(os.path.join(frontend_dir, "icons.svg")) + + + @app.get("/{full_path:path}", include_in_schema=False) + async def serve_frontend(full_path: str): + # 【重要安全修复】避免拦截不存在的 API 路由。如果是调用了不存在的 /api/ 接口,直接返回 404,不返回前端页面 + if full_path.startswith("api/"): + return JSONResponse(status_code=404, content={"detail": "API endpoint not found"}) + + index_path = os.path.join(frontend_dir, "index.html") + if os.path.exists(index_path): + return FileResponse(index_path) + return JSONResponse(status_code=404, content={"detail": "Frontend build not found"}) +else: + import logging + + logging.getLogger("pretor").warning(f"Frontend dist folder not found at {frontend_dir}. Skipping frontend mount.") + + +@serve.deployment +@serve.ingress(app) +class PretorGateway: + gateway: Dict[str, WebSocket] + + def __init__(self): + self.gateway = {} \ No newline at end of file diff --git a/pretor/core/api/__init__.py b/pretor/core/api/__init__.py deleted file mode 100644 index 7434659..0000000 --- a/pretor/core/api/__init__.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright 2026 zhaoxi826 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from typing import Dict -from fastapi import FastAPI, WebSocket, Request -from fastapi.staticfiles import StaticFiles -from fastapi.responses import FileResponse, JSONResponse - -from pretor.api.platform.frontend import client_router -from pretor.api.auth import auth_router -from pretor.api.provider import provider_router -from pretor.api.resource import resource_router -from pretor.api.cluster import cluster_router -from pretor.api.agent import agent_router -from pretor.api.workflow import workflow_router -from pretor.utils.error import ( - DemandError, ModelNotExistError, UserError, - UserNotExistError, UserPasswordError, ProviderError, - ProviderNotExistError, WorkflowError, WorkflowExit -) - -from ray import serve - -app = FastAPI() - -app.include_router(client_router) # 客户端路径 -app.include_router(auth_router) # 用户路径 -app.include_router(provider_router) # 供应商路径 -app.include_router(resource_router) # 资源路径 -app.include_router(cluster_router) # 集群信息路径 -app.include_router(agent_router) # agent路径 -app.include_router(workflow_router) # workflow路径 - -@app.exception_handler(UserNotExistError) -async def user_not_exist_handler(request: Request, exc: UserNotExistError): - return JSONResponse(status_code=404, content={"message": "用户不存在"}) - - -@app.exception_handler(UserPasswordError) -async def user_password_handler(request: Request, exc: UserPasswordError): - return JSONResponse(status_code=401, content={"message": "密码错误"}) - - -@app.exception_handler(UserError) -async def user_error_handler(request: Request, exc: UserError): - return JSONResponse(status_code=400, content={"message": "用户相关错误"}) - - -@app.exception_handler(ProviderNotExistError) -async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError): - return JSONResponse(status_code=404, content={"message": "服务提供商不存在"}) - - -@app.exception_handler(ProviderError) -async def provider_error_handler(request: Request, exc: ProviderError): - return JSONResponse(status_code=400, content={"message": "服务提供商错误"}) - - -@app.exception_handler(ModelNotExistError) -async def model_not_exist_handler(request: Request, exc: ModelNotExistError): - return JSONResponse(status_code=404, content={"message": "模型不存在"}) - - -@app.exception_handler(DemandError) -async def demand_error_handler(request: Request, exc: DemandError): - return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"}) - - -@app.exception_handler(WorkflowExit) -async def workflow_exit_handler(request: Request, exc: WorkflowExit): - return JSONResponse(status_code=400, content={"message": "工作流已退出"}) - - -@app.exception_handler(WorkflowError) -async def workflow_error_handler(request: Request, exc: WorkflowError): - return JSONResponse(status_code=500, content={"message": "工作流执行错误"}) - -base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -frontend_dir = os.path.join(base_dir, "frontend", "dist") - -if os.path.exists(frontend_dir): - app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets") - - - @app.get("/favicon.svg", include_in_schema=False) - async def serve_favicon(): - return FileResponse(os.path.join(frontend_dir, "favicon.svg")) - - - @app.get("/icons.svg", include_in_schema=False) - async def serve_icons(): - return FileResponse(os.path.join(frontend_dir, "icons.svg")) - - - @app.get("/{full_path:path}", include_in_schema=False) - async def serve_frontend(full_path: str): - # 【重要安全修复】避免拦截不存在的 API 路由。如果是调用了不存在的 /api/ 接口,直接返回 404,不返回前端页面 - if full_path.startswith("api/"): - return JSONResponse(status_code=404, content={"detail": "API endpoint not found"}) - - index_path = os.path.join(frontend_dir, "index.html") - if os.path.exists(index_path): - return FileResponse(index_path) - return JSONResponse(status_code=404, content={"detail": "Frontend build not found"}) -else: - import logging - - logging.getLogger("pretor").warning(f"Frontend dist folder not found at {frontend_dir}. Skipping frontend mount.") - - -@serve.deployment -@serve.ingress(app) -class PretorGateway: - gateway: Dict[str, WebSocket] - - def __init__(self): - self.gateway = {} \ No newline at end of file