fix(extract): 实体提取改异步(worker+轮询),根治 502 + 刷新丢 loading + 重复扣费
问题:进资产页点"提取人物/场景"——同步死等豆包思考模型(数十秒)→ 撞网关超时 502;"提取中"loading 是纯前端内存态,刷新即丢;且无幂等,刷新后重点会重复扣费。 改动(对齐全站异步三件套:出图/三视图/故事板同款): - 后端 submit_extract_entities:Web 只建 RESERVED 任务 + 预留额度秒回(不再 502); 已有在途提取则复用,绝不二次预扣(防刷新后重点 / 并发重复扣费)。 - 后端 run_extract_entities_task(Celery worker):流式调豆包 + 解析 + 落库 + 扣费; 失败退费并把可读错误记进 task.error_message。 - 新增 AITask.Type.entity_extraction(迁移 0011)+ extract-status 端点(轮询进度/成败)。 - 前端 runExtract 改提交+轮询;进资产页自动认领在途提取,刷新后 loading 自己回来。 验收:完整 Django 套件 149/149 全绿(含新增 4 测:异步落库+计费一次 / content空 回退 reasoning / 在途防重复扣费 / 状态端点成败透出);tsc + vite build 全绿; makemigrations --check 无遗漏。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
71af0ae166
commit
c8cb6d3d51
@@ -19,13 +19,14 @@ from apps.ai.services import (
|
||||
DEFAULT_VOICEOVER_VOICE,
|
||||
VOICEOVER_VOICES,
|
||||
create_export_job,
|
||||
extract_entities_for_project,
|
||||
generate_base_asset,
|
||||
generate_person_triview,
|
||||
generate_storyboard_frame,
|
||||
get_default_model,
|
||||
get_inflight_extraction,
|
||||
poll_video_segment,
|
||||
regenerate_script_segment,
|
||||
submit_extract_entities,
|
||||
submit_storyboard,
|
||||
submit_video_segment,
|
||||
synthesize_project_voiceover,
|
||||
@@ -294,15 +295,37 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="extract-entities")
|
||||
def extract_entities_action(self, request, pk=None):
|
||||
"""从已定稿脚本提取角色 / 场景实体(独立步,进资产阶段时调)。AI 读剧本 → 角色 / 场景 + 每镜 refs,
|
||||
落库覆盖 project.metadata + 每镜 entity_refs;**商品不提**(参考图层用真实主图 / 三视图无条件带上)。
|
||||
前端进资产页蒙版上「只提取 / 提取+生成」按钮点的就是它(生成由前端按返回实体循环调 generate-base-asset)。"""
|
||||
"""从已定稿脚本提取角色 / 场景实体(独立步,进资产阶段时调)——**异步**:豆包思考模型流式抽取较慢,
|
||||
Web 层只建任务秒回(不再 502),慢活交 worker(run_extract_entities_task)。前端拿 task_id 轮询
|
||||
extract-status 取结果。已有在途提取则复用(防刷新后重点重复扣费)。落库覆盖 project.metadata +
|
||||
每镜 entity_refs;**商品不提**(参考图层用真实主图)。"""
|
||||
require_worker() # 异步提取依赖 worker 兜底执行,没 worker 直接拒绝(否则任务永远 RESERVED)
|
||||
project = self.get_object()
|
||||
try:
|
||||
result = extract_entities_for_project(project=project, user=request.user)
|
||||
except ValueError as exc: # 无脚本 / 无模型 / 额度不足 / 解析失败 等,立即反馈
|
||||
task = submit_extract_entities(project=project, user=request.user)
|
||||
except ValueError as exc: # 无脚本 / 无分镜 / 无模型 / 额度不足,立即反馈
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(result)
|
||||
return Response({"task_id": str(task.id), "status": task.status})
|
||||
|
||||
@action(detail=True, methods=["get"], url_path="extract-status")
|
||||
def extract_status_action(self, request, pk=None):
|
||||
"""实体提取进度(异步)。前端进资产页 / 点提取后据此轮询 + 刷新后重建「提取中」loading:
|
||||
running=有在途任务;否则读最近一条提取任务的成败。成功时回带 entities(供「提取+生成」继续出参考图)。"""
|
||||
project = self.get_object()
|
||||
inflight = get_inflight_extraction(project)
|
||||
if inflight is not None:
|
||||
return Response({"running": True, "status": inflight.status, "error": "", "entities": []})
|
||||
last = (
|
||||
AITask.objects.filter(project=project, task_type=AITask.Type.ENTITY_EXTRACTION)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if last is None:
|
||||
return Response({"running": False, "status": "", "error": "", "entities": []})
|
||||
entities = (project.metadata or {}).get("script_entities", []) if last.status == AITask.Status.SUCCEEDED else []
|
||||
return Response(
|
||||
{"running": False, "status": last.status, "error": last.error_message or "", "entities": entities}
|
||||
)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="generate-base-asset")
|
||||
def generate_base_asset_action(self, request, pk=None):
|
||||
|
||||
Reference in New Issue
Block a user