diff --git a/apps/api/app/api/router.py b/apps/api/app/api/router.py index 0a9d7e857..4ab60b2b2 100755 --- a/apps/api/app/api/router.py +++ b/apps/api/app/api/router.py @@ -8,6 +8,7 @@ from app.api.routes.chunked_upload import router as chunked_upload_router from app.api.routes.classification_jobs import router as classification_jobs_router from app.api.routes.clips_standalone import router as clips_standalone_router from app.api.routes.cover_templates import router as cover_templates_router +from app.api.routes.drafts_standalone import router as drafts_standalone_router from app.api.routes.duplication import router as duplication_router from app.api.routes.feature_flags import router as feature_flags_router from app.api.routes.generation_cover import router as generation_cover_router @@ -19,7 +20,8 @@ from app.api.routes.health import router as health_check_router from app.api.routes.ingest_jobs import router as ingest_jobs_router from app.api.routes.internal_render import router as internal_render_router from app.api.routes.lipsync import router as lipsync_router -from app.api.routes.points import points_router, usage_router +from app.api.routes.points import router as points_router +from app.api.routes.points import usage_router from app.api.routes.projects import router as projects_router from app.api.routes.scripts import router as scripts_router from app.api.routes.scripts_ai import router as scripts_ai_router @@ -41,6 +43,19 @@ api_router = APIRouter(prefix="/api/v1") health_router = APIRouter() health_router.include_router(health_check_router) +# ── /api/health 别名:部分前端/探针把 health 放在 /api 前缀下 ────────────── +# 原来 /health 在根路径;额外加一个 /api/health 别名避免 404。 +api_health_router = APIRouter(prefix="/api") +api_health_router.include_router(health_check_router) +health_router.include_router(api_health_router) + +# ── 旧前端路径别名(无需 template_id 路径参数)──────────────────────────── +# /api/v1/clips/from-assets 已有 clips_standalone;此处额外挂 /api/v1/editor/*, +# 解决前端调 /api/v1/editor/clips/from-assets 和 /api/v1/editor/drafts 的 404。 +editor_legacy_router = APIRouter(prefix="/editor", tags=["Editor Legacy Alias"]) +editor_legacy_router.include_router(clips_standalone_router) +editor_legacy_router.include_router(drafts_standalone_router) + api_router.include_router( auth_router, tags=["Auth"], @@ -169,6 +184,9 @@ api_router.include_router( prefix="/templates/{template_id}/editor", tags=["TemplateEditor"], ) +api_router.include_router( + editor_legacy_router, +) api_router.include_router( tts_router, prefix="/tts", diff --git a/apps/api/app/api/routes/drafts_standalone.py b/apps/api/app/api/routes/drafts_standalone.py new file mode 100644 index 000000000..302e3b36d --- /dev/null +++ b/apps/api/app/api/routes/drafts_standalone.py @@ -0,0 +1,102 @@ +"""独立的草稿端点(不依赖 template_id 路径参数,兼容旧前端路径). + +提供以下别名端点,与 /api/v1/templates/{template_id}/editor/draft 功能一致: +- GET /api/v1/editor/drafts 获取草稿详情(template_id 从 query/body/默认模板兜底) +- PUT /api/v1/editor/drafts 更新草稿(兼容前端 useDraftAutoSave 调用) + +根因:前端 useDraftAutoSave 调用 /api/v1/editor/drafts(复数、无 template_id), +与后端以 template_id 为路径参数的设计不一致,导致 404 并触发 10s timeout。 +本模块参照 clips_standalone.py 的模式,通过默认模板兜底复用 draft.py 的核心逻辑。 +""" + +from __future__ import annotations + +import logging + +from app.auth import AuthenticatedUser, get_current_user +from app.dependencies import get_db_session +from app.services.edit_plan_service import EditPlanService +from app.services.edit_template_service import EditTemplateService +from fastapi import APIRouter, Depends, HTTPException, Query, status +from sqlalchemy.orm import Session + +from ._default_template import get_or_create_default_template_id +from .templates_editor.dependencies import resolve_draft_plan_id +from .templates_editor.draft import get_editor_draft, update_editor_draft +from .templates_editor.schemas import EditorDraftResponse, EditorUpdateRequest + +logger = logging.getLogger(__name__) +router = APIRouter(tags=["Editor Legacy Alias"]) + + +def _resolve_editor_services(db: Session) -> tuple[EditTemplateService, EditPlanService]: + return EditTemplateService(db), EditPlanService(db) + + +def _resolve_template_id( + template_id: str | None, + db: Session, + current_user: AuthenticatedUser, +) -> str: + """解析 template_id:query/body 优先,否则兜底默认模板。""" + tid = (template_id or "").strip() + if tid: + return tid + user_id = str(current_user.user.id) + tid = get_or_create_default_template_id(db, user_id) + if not tid: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="无法自动创建默认模板,请刷新页面重试", + ) + return tid + + +@router.get("/drafts", response_model=EditorDraftResponse) +def get_editor_drafts_alias( + template_id: str | None = Query(default=None, description="模板ID,不传则兜底默认模板"), + db: Session = Depends(get_db_session), + current_user: AuthenticatedUser = Depends(get_current_user), +) -> EditorDraftResponse: + """获取草稿详情(复数路径别名,兼容旧前端调用)。""" + tid = _resolve_template_id(template_id, db, current_user) + services = _resolve_editor_services(db) + plan_id = resolve_draft_plan_id( + template_id=tid, + services=services, + current_user=current_user, + db=db, + auto_create_default=False, + ) + return get_editor_draft( + template_id=tid, + plan_id=plan_id, + services=services, + _=current_user, + ) + + +@router.put("/drafts", response_model=EditorDraftResponse) +def update_editor_drafts_alias( + req: EditorUpdateRequest, + template_id: str | None = Query(default=None, description="模板ID,不传则兜底默认模板"), + db: Session = Depends(get_db_session), + current_user: AuthenticatedUser = Depends(get_current_user), +) -> EditorDraftResponse: + """更新草稿(复数路径别名,兼容前端 useDraftAutoSave 调用)。""" + tid = _resolve_template_id(template_id, db, current_user) + services = _resolve_editor_services(db) + plan_id = resolve_draft_plan_id( + template_id=tid, + services=services, + current_user=current_user, + db=db, + auto_create_default=False, + ) + return update_editor_draft( + template_id=tid, + req=req, + plan_id=plan_id, + services=services, + _=current_user, + ) diff --git a/apps/web/src/pages/generate/generate.css b/apps/web/src/pages/generate/generate.css index 4bbc065b4..b9eea5fe5 100644 --- a/apps/web/src/pages/generate/generate.css +++ b/apps/web/src/pages/generate/generate.css @@ -3664,14 +3664,42 @@ border: 1px solid #fff; z-index: 10; } -.xx-ce-handle--0 { top: -4px; left: -4px; } -.xx-ce-handle--1 { top: -4px; left: 50%; margin-left: -4px; } -.xx-ce-handle--2 { top: -4px; right: -4px; } -.xx-ce-handle--3 { top: 50%; right: -4px; margin-top: -4px; } -.xx-ce-handle--4 { bottom: -4px; right: -4px; } -.xx-ce-handle--5 { bottom: -4px; left: 50%; margin-left: -4px; } -.xx-ce-handle--6 { bottom: -4px; left: -4px; } -.xx-ce-handle--7 { top: 50%; left: -4px; margin-top: -4px; } +.xx-ce-handle--0 { + top: -4px; + left: -4px; +} +.xx-ce-handle--1 { + top: -4px; + left: 50%; + margin-left: -4px; +} +.xx-ce-handle--2 { + top: -4px; + right: -4px; +} +.xx-ce-handle--3 { + top: 50%; + right: -4px; + margin-top: -4px; +} +.xx-ce-handle--4 { + bottom: -4px; + right: -4px; +} +.xx-ce-handle--5 { + bottom: -4px; + left: 50%; + margin-left: -4px; +} +.xx-ce-handle--6 { + bottom: -4px; + left: -4px; +} +.xx-ce-handle--7 { + top: 50%; + left: -4px; + margin-top: -4px; +} /* Background element */ .xx-ce-el-bg {