测试极速成片

This commit is contained in:
Azmat@qq.com
2026-08-26 15:18:18 +08:00
parent 245525ec53
commit 0ee498d807
30 changed files with 1963 additions and 367 deletions
+41 -23
View File
@@ -8,7 +8,7 @@ from django.http import HttpResponse, JsonResponse, StreamingHttpResponse
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import APIException, ValidationError
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.renderers import BaseRenderer
from rest_framework.response import Response
@@ -95,6 +95,12 @@ from .tasks import poll_video_segment_task
logger = logging.getLogger(__name__)
class QuickCreateInProgress(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = "该项目正在极速成片中,请到极速成片页查看进度"
default_code = "quick_create_running"
class ServerSentEventRenderer(BaseRenderer):
"""让 DRF 内容协商接受 Accept: text/event-stream(否则流式端点直接 406)。
实际响应由视图返回 StreamingHttpResponse 直接下发,这个 renderer 只用于通过协商。"""
@@ -235,7 +241,7 @@ def settle_video_completion(project: Project) -> bool:
class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
queryset = Project.objects.select_related("product", "timeline").prefetch_related(
queryset = Project.objects.select_related("product", "timeline", "quick_create_job").prefetch_related(
"stages",
"video_segments",
"video_segments__adopted_version__asset__files",
@@ -339,7 +345,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
# ——原列表把每个项目的 阶段/片段/故事板/时间线/资产文件全拉出,20 个项目实测 ~2s。
if self.action == "list":
qs = (
Project.objects.select_related("product", "product__cover_asset", "timeline")
Project.objects.select_related("product", "product__cover_asset", "timeline", "quick_create_job")
.prefetch_related(
"product__cover_asset__files",
# 成片地址(final_video_url)只需要「成功的导出任务」,预取到位后列表不再逐项目查库
@@ -363,6 +369,21 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
).order_by("-updated_at")
return super().get_queryset().filter(is_deleted=False, purged_at__isnull=True)
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
if request.method in ("GET", "HEAD", "OPTIONS"):
return
if self.action in {"create", "destroy"}:
return
pk = kwargs.get("pk")
if not pk:
return
if QuickCreateJob.objects.filter(
project_id=pk,
status__in=[QuickCreateJob.Status.QUEUED, QuickCreateJob.Status.RUNNING],
).exists():
raise QuickCreateInProgress()
def perform_destroy(self, instance):
instance.is_deleted = True
instance.save(update_fields=["is_deleted", "updated_at"])
@@ -730,30 +751,19 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
job = self._quick_job_queryset().get(id=job.id)
return Response(QuickCreateJobSerializer(job).data)
@action(detail=False, methods=["post"], url_path=r"quick-create-retry/(?P<job_id>[^/.]+)")
def quick_create_retry(self, request, job_id=None):
job = self._quick_job_queryset().filter(id=job_id).first()
if job is None:
return Response({"detail": "极速成片任务不存在"}, status=status.HTTP_404_NOT_FOUND)
if job.status == QuickCreateJob.Status.SUCCEEDED:
return Response({"detail": "任务已经完成"}, status=status.HTTP_400_BAD_REQUEST)
if job.status == QuickCreateJob.Status.CANCELLED:
return Response({"detail": "已取消的任务请重新开始"}, status=status.HTTP_400_BAD_REQUEST)
from .services.quick_create import resume_quick_create
resume_quick_create(job)
job = self._quick_job_queryset().get(id=job.id)
return Response(QuickCreateJobSerializer(job).data)
@action(detail=False, methods=["get"], url_path="quick-create-history")
def quick_create_history(self, request):
from .services.quick_create import restore_false_failed_quick_creates
restore_false_failed_quick_creates(self.get_team())
# 进行中的任务看上方状态卡;列表要能找回失败后去专业模式继续的项目
# 进行中 / 未完成的任务回填上方表单;过往列表只放已完成成片
jobs = (
self._quick_job_queryset()
.exclude(status__in=[QuickCreateJob.Status.QUEUED, QuickCreateJob.Status.RUNNING])
.filter(
project__is_deleted=False,
project__purged_at__isnull=True,
status=QuickCreateJob.Status.SUCCEEDED,
)
.order_by("-created_at")
)
return Response({
@@ -975,8 +985,9 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
prompt=request.data.get("prompt", ""),
label=request.data.get("label", ""),
reference_asset_id=request.data.get("reference_asset_id") or None,
# 角色立绘不再自动接力三视图;三视图只由角色详情里的显式按钮生成
auto_triview=False,
# 用户点击角色 AI 生成 = 生成立绘并在完成后自动接力三视图
# 三视图任务由 worker 创建,页面刷新或离开也不会漏掉。
auto_triview=kind == BaseAssetGroup.Kind.PERSON,
)
except ValueError as exc: # 无可用模型 / 余额不足等,立即反馈
internal_kind = "user_credit_insufficient" if str(exc).strip().lower() == "insufficient credit" else ""
@@ -1453,7 +1464,14 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
# 火山/中转报错(如人脸需走素材库的 InputImageSensitiveContentDetected)→ 返回真实报错的 JSON,
# 任务仍保留原始错误供排障,但普通用户仅收到安全错误对象,不让 500 HTML 导致前端白屏。
try:
submit_video_segment(video_segment=segment, user=request.user, prompt=request.data.get("prompt", ""))
submit_video_segment(
video_segment=segment,
user=request.user,
prompt=request.data.get("prompt", ""),
model_config_id=request.data.get("model_config_id") or None,
aspect_ratio=request.data.get("aspect_ratio") or None,
resolution=request.data.get("resolution") or None,
)
except Exception as exc: # noqa: BLE001
public_error = classify_generation_error(exc, operation="video_generate")
return Response(