解决已发现问题

This commit is contained in:
Azmat@qq.com
2026-09-17 15:00:15 +08:00
parent f3c49b36be
commit 5df038c635
24 changed files with 1913 additions and 94 deletions
+145 -5
View File
@@ -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":