大量优化全能创作

This commit is contained in:
Azmat@qq.com
2026-09-16 16:07:56 +08:00
parent a95cd159db
commit 7f5307d97c
9 changed files with 1155 additions and 65 deletions
+102
View File
@@ -30,6 +30,7 @@ from .creation import (
cleanup_stale_agent_planning,
finish_agent_planning,
request_agent_cancel,
start_segmented_video_merge,
sync_generating_messages,
team_agent_busy,
)
@@ -139,6 +140,11 @@ _WANTS_CARD_RE = re.compile(
re.IGNORECASE,
)
_WANTS_PRODUCT_PICKER_RE = re.compile(
r"(?:商品|产品).{0,8}(?:列表|库)|(?:列表|库).{0,8}(?:商品|产品)",
re.IGNORECASE,
)
# 步骤确认卡下,用户往往不会去点「按这个继续」,而是自然地回一句
# 「这个还行」「就这样」「没问题」。这些是确认,不应被误判成修改意见。
_STEP_TEXT_CONFIRM_RE = re.compile(
@@ -169,6 +175,50 @@ def _pending_chat_question(conversation: CreationConversation) -> CreationMessag
return None
def _open_product_picker(
conversation: CreationConversation,
user_text: str,
pending: CreationMessage | None = None,
):
"""用户要求商品列表时,直接返回可点击的商品卡。"""
if pending is not None and not bool((pending.payload or {}).get("submitted")):
payload = dict(pending.payload or {})
payload.update({"submitted": True, "superseded_by": "product_picker"})
pending.payload = payload
pending.save(update_fields=["payload", "updated_at"])
user_message = append_message(conversation, role="user", text=user_text)
field = {
"key": "product",
"label": _ASSET_CARD_LABELS.get("product", "选择要推广的商品"),
"type": "asset",
"required": True,
"asset_types": ["product"],
}
picker = append_message(
conversation,
role="assistant",
kind=CreationMessage.Kind.ELICIT,
text=field["label"],
payload={
"interaction": "asset_picker",
"phase": "pick",
"fields": [field],
"submitted": False,
"answers": {},
},
)
conversation.agent_status = CreationConversation.AgentStatus.AWAITING_USER
conversation.save(update_fields=["agent_status", "updated_at"])
return JsonResponse({
"conversation_id": str(conversation.id),
"agent_status": conversation.agent_status,
"messages": [
CreationMessageSerializer(user_message).data,
CreationMessageSerializer(picker).data,
],
}, status=200)
def _plot_twist_depth_continuation(depth: dict) -> str:
"""让故事深度卡的回答直接进入对应的创作分支。"""
if depth.get("value") == "smart":
@@ -1694,6 +1744,28 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
raise ValidationError({"detail": "after_seq 必须是整数"}) from exc
return Response(CreationMessageSerializer(queryset, many=True).data)
@action(detail=True, methods=["post"], url_path="merge-video-segments")
def merge_video_segments(self, request, pk=None):
"""用户确认后才合并多段成片;未点击前不下载视频、更不调用 ffmpeg。"""
conversation = self.get_object()
message_id = str(request.data.get("message_id") or "").strip()
message = conversation.messages.filter(id=message_id).first() if message_id else None
if message is None:
return JsonResponse({"detail": "找不到要合并的视频片段"}, status=404)
try:
merge_task, generating = start_segmented_video_merge(
conversation=conversation, message=message, user=request.user
)
except ValueError as exc:
return JsonResponse({"detail": str(exc)}, status=400)
try:
from .tasks import merge_omni_video_segments_task
merge_omni_video_segments_task.apply_async(args=[str(merge_task.id)])
except Exception: # noqa: BLE001 - 没有 worker 时前端轮询仍可看到任务,避免重复执行 ffmpeg
logger.warning("omni video merge enqueue failed for %s", merge_task.id, exc_info=True)
return JsonResponse({"message": CreationMessageSerializer(generating).data}, status=202)
@action(detail=True, methods=["post"], url_path="cancel")
def cancel(self, request, pk=None):
"""终止进行中的整理方案(agent planning)。
@@ -1765,6 +1837,10 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
# 打招呼不应被误当成上一条素材追问的答案;让 agent 立刻简短回应即可。
elif kind == "text" and text and not is_greeting(text):
pending = _pending_chat_question(conversation)
# 无论旧会话遗留了什么追问,只要用户要商品列表,就直接出可点击卡片。
# 不能再交给模型 search_library 后用文字念出商品名。
if _WANTS_PRODUCT_PICKER_RE.search(text):
return _open_product_picker(conversation, text, pending)
if pending is not None:
payload = dict(pending.payload or {})
if payload.get("interaction") == "step_confirm":
@@ -2049,6 +2125,13 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
record_user_message = False
# force_creative / continuation 已设;跳过后面普通 elicit 逻辑
else:
if payload.get("interaction") == "selling_point_gate":
mode = str(answers.get("selling_point_mode") or "").strip().lower()
selling_point = str(answers.get("selling_point") or "").strip()
if mode not in {"manual", "auto"}:
return JsonResponse({"detail": "请选择自己填写卖点或系统推荐"}, status=400)
if mode == "manual" and not selling_point:
return JsonResponse({"detail": "请先填写一个真实卖点,或选择系统推荐"}, status=400)
payload["answers"] = answers
payload["submitted"] = True
card.payload = payload
@@ -2078,6 +2161,25 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
continuation_instruction = _plot_twist_direction_continuation(
conversation, payload, choice
)
elif payload.get("interaction") == "selling_point_gate":
mode = str(answers.get("selling_point_mode") or "").strip().lower()
selling_point = str(answers.get("selling_point") or "").strip()
memory = dict(conversation.memory or {})
memory["selling_point_ready"] = True
memory["selling_point_mode"] = mode
memory["selling_point"] = selling_point if mode == "manual" else ""
conversation.memory = memory
conversation.save(update_fields=["memory", "updated_at"])
text = ""
record_user_message = False
force_creative_turn = True
continuation_instruction = (
f"商家已确认核心卖点:【{selling_point}】。现在只调用 write_strategy 写创作策略,"
"再等待商家确认;策略、后续方案和视频都必须围绕这个真实卖点,不要替换或夸大。"
if mode == "manual"
else "商家选择系统推荐卖点。现在只调用 write_strategy 写创作策略;"
"从商品资料和现有素材中挑一个最容易被画面证明的真实核心卖点,不要虚构功效、价格或规格。"
)
elif payload.get("phase") == "gate":
choice = str(answers.get("_asset_gate") or "").strip()
if choice == "send":