完成视频复刻和优化
This commit is contained in:
@@ -170,14 +170,14 @@ def _public_error(raw: str) -> str:
|
||||
text = (raw or "").strip()
|
||||
lower = text.lower()
|
||||
if QUICK_SCRIPT_MODEL_NAME in lower:
|
||||
return "极速成片需要的豆包 Seed 2.1 Pro 模型未启用,请联系管理员配置"
|
||||
return "一键成片需要的豆包 Seed 2.1 Pro 模型未启用,请联系管理员配置"
|
||||
if "insufficient credit" in lower or "额度不足" in text:
|
||||
return "可用积分不足,极速成片已暂停"
|
||||
return "可用积分不足,一键成片已暂停"
|
||||
if "no active" in lower or "not configured" in lower or "没有可用" in text:
|
||||
return "当前缺少可用的生成模型,请联系管理员配置"
|
||||
if _looks_like_review_error(text):
|
||||
return REVIEW_FAIL_MESSAGE
|
||||
return "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
return "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
|
||||
|
||||
def _safe_error(exc: Exception) -> str:
|
||||
@@ -301,7 +301,7 @@ def fail_quick_create(job: QuickCreateJob, message: str, *, internal_error: str
|
||||
job.refresh_from_db(fields=["status", "metadata", "phase"])
|
||||
if job.status in {QuickCreateJob.Status.SUCCEEDED, QuickCreateJob.Status.CANCELLED}:
|
||||
return
|
||||
public_message = (message or "极速成片暂未完成,请稍后重试").strip()[:500]
|
||||
public_message = (message or "一键成片暂未完成,请稍后重试").strip()[:500]
|
||||
metadata = dict(job.metadata or {})
|
||||
if internal_error:
|
||||
metadata["internal_error"] = internal_error[:2000]
|
||||
@@ -365,7 +365,7 @@ def _consume_script_agent(job: QuickCreateJob) -> None:
|
||||
settings = _quick_settings(project)
|
||||
user = job.created_by or project.created_by
|
||||
if user is None:
|
||||
raise ValueError("极速成片任务缺少创建人")
|
||||
raise ValueError("一键成片任务缺少创建人")
|
||||
model_config = get_quick_script_model()
|
||||
if model_config is None:
|
||||
raise ValueError(f"{QUICK_SCRIPT_MODEL_NAME} is not configured")
|
||||
@@ -376,7 +376,8 @@ def _consume_script_agent(job: QuickCreateJob) -> None:
|
||||
getattr(product, "category", "") or "",
|
||||
getattr(product, "title", "") or "",
|
||||
)
|
||||
fmt = wizard.get("presentation_format") or rec["format"]
|
||||
# 极速成片与专业创作一致,脚本表现形式统一为口播。
|
||||
fmt = "oral"
|
||||
structure = wizard.get("video_structure") or rec["structure"]
|
||||
persona = wizard.get("persona") or rec["persona"]
|
||||
format_label = PRESENTATION_FORMATS.get(fmt, fmt)
|
||||
@@ -486,7 +487,7 @@ def _advance_product(job: QuickCreateJob) -> int:
|
||||
ProductSellingPoint.objects.create(
|
||||
product=product,
|
||||
title=product.title,
|
||||
detail="由极速成片根据商品名称自动填写",
|
||||
detail="由一键成片根据商品名称自动填写",
|
||||
sort_order=0,
|
||||
)
|
||||
_save_job(
|
||||
@@ -695,7 +696,7 @@ def _start_base_assets(job: QuickCreateJob) -> None:
|
||||
project = job.project
|
||||
user = job.created_by or project.created_by
|
||||
if user is None:
|
||||
raise ValueError("极速成片任务缺少创建人")
|
||||
raise ValueError("一键成片任务缺少创建人")
|
||||
with transaction.atomic():
|
||||
job = QuickCreateJob.objects.select_for_update().select_related("project").get(id=job.id)
|
||||
metadata = dict(job.metadata or {})
|
||||
@@ -939,7 +940,7 @@ def _start_videos(job: QuickCreateJob) -> None:
|
||||
submit_video_segment(
|
||||
video_segment=segment,
|
||||
user=job.created_by or job.project.created_by,
|
||||
prompt="极速成片自动生成,严格遵循本镜故事板与脚本。",
|
||||
prompt="一键成片自动生成,严格遵循本镜故事板与脚本。",
|
||||
model_config_id=settings["video_model_config_id"] or None,
|
||||
aspect_ratio=settings["aspect_ratio"],
|
||||
resolution=settings["resolution"],
|
||||
|
||||
@@ -307,14 +307,14 @@ class QuickCreateApiTests(TestCase):
|
||||
|
||||
def test_retry_api_resumes_failed_job(self):
|
||||
product = Product.objects.create(team=self.team, created_by=self.user, title="续跑商品")
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=product, name="续跑商品 · 极速成片")
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=product, name="续跑商品 · 一键成片")
|
||||
job = QuickCreateJob.objects.create(
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
project=project,
|
||||
status=QuickCreateJob.Status.FAILED,
|
||||
phase=QuickCreateJob.Phase.PRODUCTION,
|
||||
message="极速成片暂未完成,请稍后重试或进入专业模式查看",
|
||||
message="一键成片暂未完成,请稍后重试或进入专业模式查看",
|
||||
)
|
||||
with patch("apps.projects.tasks.advance_quick_create_task.apply_async") as enqueue:
|
||||
response = self.client.post(f"/api/projects/quick-create-retry/{job.id}/")
|
||||
@@ -325,7 +325,7 @@ class QuickCreateApiTests(TestCase):
|
||||
|
||||
def test_history_lists_completed_jobs_only(self):
|
||||
product = Product.objects.create(team=self.team, created_by=self.user, title="历史商品")
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=product, name="历史商品 · 极速成片")
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=product, name="历史商品 · 一键成片")
|
||||
QuickCreateJob.objects.create(
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
@@ -334,17 +334,17 @@ class QuickCreateApiTests(TestCase):
|
||||
phase=QuickCreateJob.Phase.COMPLETE,
|
||||
)
|
||||
failed_product = Product.objects.create(team=self.team, created_by=self.user, title="失败商品")
|
||||
failed_project = Project.objects.create(team=self.team, created_by=self.user, product=failed_product, name="失败商品 · 极速成片")
|
||||
failed_project = Project.objects.create(team=self.team, created_by=self.user, product=failed_product, name="失败商品 · 一键成片")
|
||||
QuickCreateJob.objects.create(
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
project=failed_project,
|
||||
status=QuickCreateJob.Status.FAILED,
|
||||
phase=QuickCreateJob.Phase.PRODUCTION,
|
||||
message="极速成片暂未完成,请稍后重试或进入专业模式查看",
|
||||
message="一键成片暂未完成,请稍后重试或进入专业模式查看",
|
||||
)
|
||||
running_product = Product.objects.create(team=self.team, created_by=self.user, title="进行中商品")
|
||||
running_project = Project.objects.create(team=self.team, created_by=self.user, product=running_product, name="进行中商品 · 极速成片")
|
||||
running_project = Project.objects.create(team=self.team, created_by=self.user, product=running_product, name="进行中商品 · 一键成片")
|
||||
QuickCreateJob.objects.create(
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
@@ -369,13 +369,13 @@ class QuickCreateApiTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["count"], 1)
|
||||
titles = [item["title"] for item in response.data["results"]]
|
||||
self.assertIn("历史商品 · 极速成片", titles)
|
||||
self.assertNotIn("失败商品 · 极速成片", titles)
|
||||
self.assertNotIn("进行中商品 · 极速成片", titles)
|
||||
self.assertIn("历史商品 · 一键成片", titles)
|
||||
self.assertNotIn("失败商品 · 一键成片", titles)
|
||||
self.assertNotIn("进行中商品 · 一键成片", titles)
|
||||
|
||||
def test_history_hides_deleted_projects(self):
|
||||
product = Product.objects.create(team=self.team, created_by=self.user, title="还在的商品")
|
||||
alive = Project.objects.create(team=self.team, created_by=self.user, product=product, name="还在的商品 · 极速成片")
|
||||
alive = Project.objects.create(team=self.team, created_by=self.user, product=product, name="还在的商品 · 一键成片")
|
||||
QuickCreateJob.objects.create(
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
@@ -388,7 +388,7 @@ class QuickCreateApiTests(TestCase):
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
product=deleted_product,
|
||||
name="已删商品 · 极速成片",
|
||||
name="已删商品 · 一键成片",
|
||||
is_deleted=True,
|
||||
)
|
||||
QuickCreateJob.objects.create(
|
||||
@@ -403,8 +403,8 @@ class QuickCreateApiTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
titles = [item["title"] for item in response.data["results"]]
|
||||
self.assertEqual(response.data["count"], 1)
|
||||
self.assertIn("还在的商品 · 极速成片", titles)
|
||||
self.assertNotIn("已删商品 · 极速成片", titles)
|
||||
self.assertIn("还在的商品 · 一键成片", titles)
|
||||
self.assertNotIn("已删商品 · 一键成片", titles)
|
||||
|
||||
def test_list_serializer_flags_quick_create_projects(self):
|
||||
product = Product.objects.create(team=self.team, created_by=self.user, title="列表商品")
|
||||
@@ -412,7 +412,7 @@ class QuickCreateApiTests(TestCase):
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
product=product,
|
||||
name="列表商品 · 极速成片",
|
||||
name="列表商品 · 一键成片",
|
||||
metadata={"quick_create": True},
|
||||
)
|
||||
normal = Project.objects.create(
|
||||
@@ -436,7 +436,7 @@ class QuickCreateApiTests(TestCase):
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
product=product,
|
||||
name="锁单商品 · 极速成片",
|
||||
name="锁单商品 · 一键成片",
|
||||
metadata={"quick_create": True},
|
||||
)
|
||||
job = QuickCreateJob.objects.create(
|
||||
@@ -448,7 +448,7 @@ class QuickCreateApiTests(TestCase):
|
||||
|
||||
blocked = self.client.patch(f"/api/projects/{project.id}/", {"name": "不该改"}, format="json")
|
||||
self.assertEqual(blocked.status_code, 409)
|
||||
self.assertIn("极速成片", str(blocked.data))
|
||||
self.assertIn("一键成片", str(blocked.data))
|
||||
|
||||
allowed = self.client.get(f"/api/projects/{project.id}/")
|
||||
self.assertEqual(allowed.status_code, 200)
|
||||
@@ -473,7 +473,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
product=self.product,
|
||||
name="测试精华 · 极速成片",
|
||||
name="测试精华 · 一键成片",
|
||||
status=Project.Status.SCRIPTING,
|
||||
current_stage=ProjectStage.Stage.SCRIPT,
|
||||
)
|
||||
@@ -554,7 +554,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
QuickCreateJob.objects.filter(id=self.job.id).update(
|
||||
status=QuickCreateJob.Status.QUEUED,
|
||||
phase=QuickCreateJob.Phase.PRODUCT,
|
||||
message="等待开始极速成片",
|
||||
message="等待开始一键成片",
|
||||
updated_at=timezone.now() - timedelta(seconds=30),
|
||||
)
|
||||
self.job.refresh_from_db()
|
||||
@@ -748,7 +748,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
self.job.metadata = {"storyboard_started": True}
|
||||
self.job.save(update_fields=["status", "phase", "metadata", "updated_at"])
|
||||
|
||||
fail_quick_create(self.job, "极速成片暂未完成,请稍后重试或进入专业模式查看", internal_error="Timeout reading from socket")
|
||||
fail_quick_create(self.job, "一键成片暂未完成,请稍后重试或进入专业模式查看", internal_error="Timeout reading from socket")
|
||||
self.job.refresh_from_db()
|
||||
self.project.refresh_from_db()
|
||||
self.assertEqual(self.job.status, QuickCreateJob.Status.FAILED)
|
||||
@@ -772,7 +772,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
def test_recover_clears_false_failed_project_before_video_starts(self):
|
||||
self.project.status = Project.Status.FAILED
|
||||
self.project.current_stage = ProjectStage.Stage.VIDEO
|
||||
self.project.failure_reason = "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.project.failure_reason = "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.project.save(update_fields=["status", "current_stage", "failure_reason", "updated_at"])
|
||||
self.job.status = QuickCreateJob.Status.FAILED
|
||||
self.job.phase = QuickCreateJob.Phase.PRODUCTION
|
||||
@@ -805,7 +805,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
def test_resume_failed_production_job_keeps_progress(self, enqueue):
|
||||
self.job.status = QuickCreateJob.Status.FAILED
|
||||
self.job.phase = QuickCreateJob.Phase.PRODUCTION
|
||||
self.job.error_message = "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.error_message = "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.metadata = {"storyboard_started": True, "transient_retries": 8, "video_fail_retries": 2}
|
||||
self.job.save(update_fields=["status", "phase", "error_message", "metadata", "updated_at"])
|
||||
|
||||
@@ -859,7 +859,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
def test_failed_job_serializer_rewrites_hidden_moderation_error(self):
|
||||
self.job.status = QuickCreateJob.Status.FAILED
|
||||
self.job.phase = QuickCreateJob.Phase.PRODUCTION
|
||||
self.job.error_message = "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.error_message = "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.metadata = {"internal_error": "400 moderation_blocked safety_violations=[sexual]"}
|
||||
self.job.save(update_fields=["status", "phase", "error_message", "metadata", "updated_at"])
|
||||
data = QuickCreateJobSerializer(self.job).data
|
||||
@@ -882,7 +882,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
segment.save(update_fields=["adopted_version", "status", "updated_at"])
|
||||
self.job.status = QuickCreateJob.Status.FAILED
|
||||
self.job.phase = QuickCreateJob.Phase.PRODUCTION
|
||||
self.job.error_message = "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.error_message = "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.save(update_fields=["status", "phase", "error_message", "updated_at"])
|
||||
|
||||
recover_quick_create(self.job)
|
||||
@@ -894,7 +894,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
"""专业模式完成后,极速任务的旧失败状态必须自动被回收。"""
|
||||
self.job.status = QuickCreateJob.Status.FAILED
|
||||
self.job.phase = QuickCreateJob.Phase.PRODUCTION
|
||||
self.job.error_message = "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.error_message = "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.save(update_fields=["status", "phase", "error_message", "updated_at"])
|
||||
self.project.status = Project.Status.COMPLETED
|
||||
self.project.current_stage = ProjectStage.Stage.VIDEO
|
||||
@@ -1143,7 +1143,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
base_ids = self._ready_asset_tasks()
|
||||
self.job.status = QuickCreateJob.Status.FAILED
|
||||
self.job.phase = QuickCreateJob.Phase.ASSETS
|
||||
self.job.error_message = "极速成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.error_message = "一键成片暂未完成,请稍后重试或进入专业模式查看"
|
||||
self.job.metadata = {
|
||||
"base_asset_task_ids": base_ids,
|
||||
"assets_started": True,
|
||||
@@ -1194,7 +1194,7 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
asset = Asset.objects.create(
|
||||
team=self.team,
|
||||
created_by=self.user,
|
||||
name="极速成片",
|
||||
name="一键成片",
|
||||
asset_type=Asset.Type.VIDEO,
|
||||
source=Asset.Source.AI_GENERATED,
|
||||
category=Asset.Category.VIDEO_CLIP,
|
||||
|
||||
@@ -98,7 +98,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class QuickCreateInProgress(APIException):
|
||||
status_code = status.HTTP_409_CONFLICT
|
||||
default_detail = "该项目正在极速成片中,请到极速成片页查看进度"
|
||||
default_detail = "该项目正在一键成片中,请到一键成片页查看进度"
|
||||
default_code = "quick_create_running"
|
||||
|
||||
|
||||
@@ -553,7 +553,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
]
|
||||
if text_model is None:
|
||||
return Response(
|
||||
{"detail": "极速成片需要的豆包 Seed 2.1 Pro 模型未启用,请联系管理员配置"},
|
||||
{"detail": "一键成片需要的豆包 Seed 2.1 Pro 模型未启用,请联系管理员配置"},
|
||||
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
)
|
||||
if missing:
|
||||
@@ -653,7 +653,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
team=team,
|
||||
created_by=request.user,
|
||||
product=product,
|
||||
name=f"{name} · 极速成片",
|
||||
name=f"{name} · 一键成片",
|
||||
status=Project.Status.SCRIPTING,
|
||||
current_stage=ProjectStage.Stage.SCRIPT,
|
||||
metadata={
|
||||
@@ -665,7 +665,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
"video_model_config_id": str(video_model.id),
|
||||
"video_model_name": video_model.name,
|
||||
"video_model_label": video_model.display_name,
|
||||
"presentation_format": rec["format"],
|
||||
"presentation_format": "oral",
|
||||
"video_structure": rec["structure"],
|
||||
"persona": rec["persona"],
|
||||
},
|
||||
@@ -676,7 +676,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
ProductSellingPoint.objects.create(
|
||||
product=product,
|
||||
title=name,
|
||||
detail="由极速成片根据商品名称自动填写",
|
||||
detail="由一键成片根据商品名称自动填写",
|
||||
sort_order=0,
|
||||
)
|
||||
job = QuickCreateJob.objects.create(
|
||||
@@ -686,7 +686,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
status=QuickCreateJob.Status.QUEUED,
|
||||
phase=QuickCreateJob.Phase.PRODUCT,
|
||||
progress=0,
|
||||
message="等待开始极速成片",
|
||||
message="等待开始一键成片",
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -722,7 +722,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
def quick_create_status(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)
|
||||
return Response({"detail": "一键成片任务不存在"}, status=status.HTTP_404_NOT_FOUND)
|
||||
from .services.quick_create import recover_quick_create
|
||||
|
||||
try:
|
||||
@@ -736,7 +736,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
def quick_create_cancel(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)
|
||||
return Response({"detail": "一键成片任务不存在"}, status=status.HTTP_404_NOT_FOUND)
|
||||
if job.status == QuickCreateJob.Status.SUCCEEDED:
|
||||
return Response({"detail": "已完成的任务不能取消"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
from .services.quick_create import cancel_quick_create
|
||||
@@ -749,7 +749,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
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)
|
||||
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:
|
||||
@@ -808,14 +808,13 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
"template_outline": render_outline_text({"outline": template.outline, "cta": template.cta}),
|
||||
}
|
||||
)
|
||||
# 套路参数只在模板真有值时覆盖,空值不要把设定卡的智能推荐顶掉。
|
||||
# 旧模板可能存了中文标签(「短剧」),这里归一成 key 再写入 wizard。
|
||||
# 表现形式已统一为口播;旧模板里的短剧/Vlog 只兼容读取结构,不能覆盖当前规则。
|
||||
raw_format = getattr(template, "presentation_format", "") or ""
|
||||
raw_structure = getattr(template, "video_structure", "") or ""
|
||||
if raw_format or raw_structure:
|
||||
fmt, structure = coerce_template_combo(raw_format, raw_structure)
|
||||
wizard["presentation_format"] = fmt
|
||||
_fmt, structure = coerce_template_combo(raw_format, raw_structure)
|
||||
wizard["video_structure"] = structure
|
||||
wizard["presentation_format"] = "oral"
|
||||
persona = coerce_persona(getattr(template, "persona", "") or "")
|
||||
if persona:
|
||||
wizard["persona"] = persona
|
||||
@@ -860,7 +859,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
"""对话式脚本 agent · 流式(SSE)。出稿 + 改稿一体,多模型可选。
|
||||
请求体:mode(auto|theme|revise)、prompt、model_config_id、selling_point_ids、persona、
|
||||
base_version_id(改稿)、aspect_ratio、total_duration(15/30/45/60)、
|
||||
presentation_format(oral|drama|vlog)、video_structure(pain|contrast|review|scene)。
|
||||
video_structure(pain|contrast|review|scene)。表现形式固定为口播。
|
||||
响应:text/event-stream,逐帧吐 tool/delta/draft/saved/done/error。"""
|
||||
project = self.get_object()
|
||||
mode = str(request.data.get("mode") or "auto")
|
||||
@@ -870,7 +869,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
# 非法值一律由 agent 侧 coerce 兜底(夹区间/回落默认),这里不做 400,避免生成被参数噪声打断
|
||||
total_duration = coerce_total_duration(request.data.get("total_duration"))
|
||||
presentation_format, video_structure = combo_keys(
|
||||
request.data.get("presentation_format"),
|
||||
"oral",
|
||||
request.data.get("video_structure"),
|
||||
)
|
||||
wizard = ((project.metadata or {}).get("wizard") if isinstance(project.metadata, dict) else None) or {}
|
||||
|
||||
Reference in New Issue
Block a user