解决已发现问题
This commit is contained in:
@@ -16,7 +16,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from apps.assets.models import Asset
|
||||
from apps.assets.models import Asset, Model as AssetModel
|
||||
from apps.assets.serializers import AssetFileSerializer, AssetSerializer
|
||||
from apps.common.api import TeamScopedViewSetMixin, get_current_team
|
||||
from apps.common.celery_health import require_worker, require_worker_task
|
||||
@@ -29,6 +29,7 @@ from .creation import (
|
||||
begin_agent_planning,
|
||||
cleanup_stale_agent_planning,
|
||||
finish_agent_planning,
|
||||
pin_refs,
|
||||
request_agent_cancel,
|
||||
start_segmented_video_merge,
|
||||
sync_generating_messages,
|
||||
@@ -37,6 +38,7 @@ from .creation import (
|
||||
from .creation_agent import (
|
||||
_ASSET_CARD_LABELS,
|
||||
_RESTART_CONTINUATION,
|
||||
apply_pain_point_direction,
|
||||
apply_confirm_params,
|
||||
apply_restart_intent,
|
||||
apply_session_params,
|
||||
@@ -44,11 +46,14 @@ from .creation_agent import (
|
||||
emit_final_confirm_gate,
|
||||
set_plot_twist_story_depth,
|
||||
is_greeting,
|
||||
is_pain_point_conversation,
|
||||
is_pain_point_direction_payload,
|
||||
is_restart_intent,
|
||||
restore_gated_step_after_cancel,
|
||||
set_video_gate_stage,
|
||||
submit_confirmed_image,
|
||||
submit_confirmed_video,
|
||||
submit_generated_person_reference,
|
||||
)
|
||||
from .tasks import run_creation_agent_turn_task
|
||||
from .mentions import TYPE_LABELS, VALID_TYPES, refs_from_elicit_answers, search_mentions
|
||||
@@ -269,6 +274,20 @@ def _plot_twist_direction_continuation(
|
||||
)
|
||||
|
||||
|
||||
def _store_click_swap_sequence(conversation: CreationConversation, value: str) -> str:
|
||||
sequence = str(value or "").strip()
|
||||
memory = dict(conversation.memory or {})
|
||||
memory["click_swap_ready"] = True
|
||||
memory["click_swap_sequence"] = sequence
|
||||
conversation.memory = memory
|
||||
conversation.save(update_fields=["memory", "updated_at"])
|
||||
return (
|
||||
f"商家已确认点击换款顺序:【{sequence}】。"
|
||||
"严格使用固定机位、同一背景和同一商品中心位置,每次由一根手指清晰点击后原位切换到下一款。"
|
||||
"现在只调用 write_strategy 写创作策略;禁止改成口播、剧情、换场景或普通使用演示。"
|
||||
)
|
||||
|
||||
|
||||
_STEP_CONTINUE_INSTRUCTIONS = {
|
||||
"strategy": (
|
||||
"用户已确认创作策略。现在只调用 write_plan 写方案卡(含完整 video_prompt 存档);"
|
||||
@@ -1893,6 +1912,18 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
continuation_instruction = _plot_twist_direction_continuation(
|
||||
conversation, payload, choice
|
||||
)
|
||||
elif payload.get("interaction") == "click_swap_sku_gate":
|
||||
sequence = text.strip()
|
||||
if sequence:
|
||||
payload["answers"] = {"sku_sequence": sequence}
|
||||
payload["submitted"] = True
|
||||
payload["answered_via"] = "chat"
|
||||
pending.payload = payload
|
||||
pending.save(update_fields=["payload", "updated_at"])
|
||||
text = ""
|
||||
record_user_message = False
|
||||
force_creative_turn = True
|
||||
continuation_instruction = _store_click_swap_sequence(conversation, sequence)
|
||||
elif payload.get("phase") == "gate":
|
||||
pending_fields = [item for item in (payload.get("pending_fields") or []) if isinstance(item, dict)]
|
||||
primary_field = pending_fields[0] if pending_fields else {}
|
||||
@@ -2034,10 +2065,19 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
existing.add(mark)
|
||||
|
||||
force_creative_turn = True
|
||||
continuation_instruction = (
|
||||
"用户刚用输入框回答了你上一句问题。直接基于这条真实回答继续原任务;"
|
||||
"不要复述答案,不要回复收到,也不要问要不要继续或要不要生成。"
|
||||
)
|
||||
if is_pain_point_conversation(conversation) and is_pain_point_direction_payload(payload):
|
||||
field_key = str(field.get("key") or "pain_point_direction")
|
||||
choice = str(answers.get(field_key) or "").strip()
|
||||
text = ""
|
||||
record_user_message = False
|
||||
continuation_instruction = apply_pain_point_direction(
|
||||
conversation, payload, choice
|
||||
)
|
||||
else:
|
||||
continuation_instruction = (
|
||||
"用户刚用输入框回答了你上一句问题。直接基于这条真实回答继续原任务;"
|
||||
"不要复述答案,不要回复收到,也不要问要不要继续或要不要生成。"
|
||||
)
|
||||
if params_changed:
|
||||
continuation_instruction += " 会话参数已更新,旧方案作废,按新参数重新产出。"
|
||||
|
||||
@@ -2132,6 +2172,46 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
return JsonResponse({"detail": "请选择自己填写卖点或系统推荐"}, status=400)
|
||||
if mode == "manual" and not selling_point:
|
||||
return JsonResponse({"detail": "请先填写一个真实卖点,或选择系统推荐"}, status=400)
|
||||
if payload.get("interaction") == "person_source_gate":
|
||||
source = str(answers.get("person_source") or "").strip()
|
||||
if source not in {"local_upload", "model_library", "platform_generate"}:
|
||||
return JsonResponse({"detail": "请选择本地上传、模特库或平台生成"}, status=400)
|
||||
if source == "local_upload":
|
||||
candidates = [
|
||||
ref for ref in refs
|
||||
if isinstance(ref, dict) and ref.get("type") == "character" and ref.get("id")
|
||||
]
|
||||
valid = any(
|
||||
Asset.objects.filter(
|
||||
team=conversation.team,
|
||||
id=ref.get("id"),
|
||||
is_deleted=False,
|
||||
purged_at__isnull=True,
|
||||
).exists()
|
||||
for ref in candidates
|
||||
)
|
||||
if not valid:
|
||||
return JsonResponse({"detail": "请先上传一张人物参考图"}, status=400)
|
||||
elif source == "model_library":
|
||||
candidates = [
|
||||
ref for ref in refs
|
||||
if isinstance(ref, dict) and ref.get("type") == "model" and ref.get("id")
|
||||
]
|
||||
valid = any(
|
||||
AssetModel.objects.filter(
|
||||
team=conversation.team,
|
||||
id=ref.get("id"),
|
||||
is_deleted=False,
|
||||
purged_at__isnull=True,
|
||||
).exists()
|
||||
for ref in candidates
|
||||
)
|
||||
if not valid:
|
||||
return JsonResponse({"detail": "请先从模特库选择一位人物"}, status=400)
|
||||
if payload.get("interaction") == "click_swap_sku_gate":
|
||||
sequence = str(answers.get("sku_sequence") or "").strip()
|
||||
if not sequence:
|
||||
return JsonResponse({"detail": "请填写要展示的款式和切换顺序"}, status=400)
|
||||
payload["answers"] = answers
|
||||
payload["submitted"] = True
|
||||
card.payload = payload
|
||||
@@ -2161,6 +2241,18 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
continuation_instruction = _plot_twist_direction_continuation(
|
||||
conversation, payload, choice
|
||||
)
|
||||
elif is_pain_point_conversation(conversation) and is_pain_point_direction_payload(payload):
|
||||
fields = [item for item in (payload.get("fields") or []) if isinstance(item, dict)]
|
||||
field_key = str((fields[0] if fields else {}).get("key") or "pain_point_direction")
|
||||
choice = str(answers.get(field_key) or "").strip()
|
||||
if not choice:
|
||||
return JsonResponse({"detail": "请选择一个痛点方向"}, status=400)
|
||||
text = ""
|
||||
record_user_message = False
|
||||
force_creative_turn = True
|
||||
continuation_instruction = apply_pain_point_direction(
|
||||
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()
|
||||
@@ -2180,6 +2272,54 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
else "商家选择系统推荐卖点。现在只调用 write_strategy 写创作策略;"
|
||||
"从商品资料和现有素材中挑一个最容易被画面证明的真实核心卖点,不要虚构功效、价格或规格。"
|
||||
)
|
||||
elif payload.get("interaction") == "click_swap_sku_gate":
|
||||
sequence = str(answers.get("sku_sequence") or "").strip()
|
||||
text = ""
|
||||
record_user_message = False
|
||||
force_creative_turn = True
|
||||
continuation_instruction = _store_click_swap_sequence(conversation, sequence)
|
||||
elif payload.get("interaction") == "person_source_gate":
|
||||
source = str(answers.get("person_source") or "").strip()
|
||||
if source == "platform_generate":
|
||||
try:
|
||||
generating = submit_generated_person_reference(
|
||||
conversation=conversation,
|
||||
user=request.user,
|
||||
)
|
||||
except ValueError as exc:
|
||||
# 生图没提交成功,把闸门放回去供用户换方式或重试。
|
||||
payload["submitted"] = False
|
||||
payload["answers"] = {}
|
||||
card.payload = payload
|
||||
card.save(update_fields=["payload", "updated_at"])
|
||||
return JsonResponse({"detail": str(exc)}, status=400)
|
||||
return JsonResponse({
|
||||
"conversation_id": str(conversation.id),
|
||||
"agent_status": conversation.agent_status,
|
||||
"messages": [CreationMessageSerializer(generating).data],
|
||||
}, status=202)
|
||||
|
||||
# 上传/模特库的人物立即进入实体锁定,不等 Celery turn 开始才保存。
|
||||
# 这样即使用户刷新,后续 60s 两段也仍能取到同一张人物图。
|
||||
person_refs = [
|
||||
ref for ref in refs
|
||||
if isinstance(ref, dict) and ref.get("type") in {"character", "model"} and ref.get("id")
|
||||
]
|
||||
pin_refs(conversation, person_refs)
|
||||
memory = dict(conversation.memory or {})
|
||||
memory["person_source"] = source
|
||||
memory["person_source_ready"] = True
|
||||
memory["person_source_pending"] = False
|
||||
conversation.memory = memory
|
||||
conversation.status = CreationConversation.Status.RUNNING
|
||||
conversation.save(update_fields=["memory", "status", "updated_at"])
|
||||
text = ""
|
||||
record_user_message = False
|
||||
force_creative_turn = True
|
||||
continuation_instruction = (
|
||||
"用户已选定出镜人物,该人物已作为整条视频的固定身份参考。"
|
||||
"直接继续创作;所有镜头和分段保持同一人,不要再追问人物来源。"
|
||||
)
|
||||
elif payload.get("phase") == "gate":
|
||||
choice = str(answers.get("_asset_gate") or "").strip()
|
||||
if choice == "send":
|
||||
|
||||
Reference in New Issue
Block a user