大量优化全能创作
This commit is contained in:
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user