修改全能创作
This commit is contained in:
@@ -27,7 +27,18 @@ from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import transaction
|
||||
|
||||
from .creation import append_message, pin_refs
|
||||
from .creation_presets import apply_image_preset_prompt, preset_guidance, preset_workflow_guidance
|
||||
from .creation_presets import (
|
||||
PLOT_TWIST_PRESET,
|
||||
PLOT_TWIST_STORY_DEPTH_OPTIONS,
|
||||
apply_image_preset_prompt,
|
||||
apply_plot_twist_story_contract,
|
||||
apply_video_preset_prompt,
|
||||
plot_twist_story_depth,
|
||||
plot_twist_story_contract,
|
||||
preset_guidance,
|
||||
preset_workflow_guidance,
|
||||
video_preset_delivery_contract,
|
||||
)
|
||||
from .mentions import TYPE_LABELS, infer_field_types, resolve_refs, search_mentions
|
||||
from .models import CreationConversation, CreationMessage, ModelConfig
|
||||
from .services import build_provider, get_default_model, get_seed_text_model, resolve_text_model
|
||||
@@ -41,8 +52,7 @@ MAX_TOOL_ROUNDS = 8
|
||||
MAX_BILLED_GENERATIONS = 1
|
||||
|
||||
# 视频闸门阶段(落在 conversation.memory.stage;resume 靠它)
|
||||
# prompt 仅兼容历史会话。新视频链路在方案确认后直接转入出片确认,Prompt 始终由平台在后台维护。
|
||||
# clarify → strategy → plan → confirm → done
|
||||
# clarify → strategy → plan → prompt → confirm → done
|
||||
VIDEO_GATE_STAGES = ("clarify", "strategy", "plan", "prompt", "confirm", "done")
|
||||
_STEP_CONFIRM_LABELS = {
|
||||
"strategy": "创作策略已写好。确认后继续写方案;要改就点「我想改」或直接说改哪里。",
|
||||
@@ -51,6 +61,146 @@ _STEP_CONFIRM_LABELS = {
|
||||
}
|
||||
|
||||
|
||||
def is_plot_twist_conversation(conversation: CreationConversation) -> bool:
|
||||
return conversation.mode == CreationConversation.Mode.VIDEO and conversation.preset == PLOT_TWIST_PRESET
|
||||
|
||||
|
||||
def active_plot_twist_story_depth(conversation: CreationConversation) -> str:
|
||||
"""时长是最终事实来源;用户中途改时长后,故事结构必须随之切换。"""
|
||||
from_duration = plot_twist_story_depth(str((conversation.params or {}).get("duration") or ""))
|
||||
if from_duration and from_duration["value"] != "smart":
|
||||
return str(from_duration["value"])
|
||||
memory = conversation.memory if isinstance(conversation.memory, dict) else {}
|
||||
return str(memory.get("plot_twist_story_depth") or "").strip()
|
||||
|
||||
|
||||
def set_plot_twist_story_depth(conversation: CreationConversation, value: str) -> dict | None:
|
||||
"""卡片或自然语言选时长后,同步会话顶部参数与最终 Prompt 的故事结构。"""
|
||||
if not is_plot_twist_conversation(conversation):
|
||||
return None
|
||||
depth = plot_twist_story_depth(value)
|
||||
if depth is None:
|
||||
return None
|
||||
memory = dict(conversation.memory or {})
|
||||
params = dict(conversation.params or {})
|
||||
memory["plot_twist_story_depth"] = depth["value"]
|
||||
if depth["duration"]:
|
||||
params["duration"] = depth["duration"]
|
||||
conversation.memory = memory
|
||||
conversation.params = params
|
||||
conversation.save(update_fields=["memory", "params", "updated_at"])
|
||||
return depth
|
||||
|
||||
|
||||
def append_plot_twist_story_depth_question(conversation: CreationConversation) -> CreationMessage:
|
||||
return append_message(
|
||||
conversation,
|
||||
role="assistant",
|
||||
kind=CreationMessage.Kind.ELICIT,
|
||||
text="你希望这支剧情带货视频做到什么程度?",
|
||||
payload={
|
||||
"interaction": "plot_twist_story_depth",
|
||||
"fields": [
|
||||
{
|
||||
"key": "story_depth",
|
||||
"label": "故事深度选择",
|
||||
"type": "single",
|
||||
"required": True,
|
||||
"options": [
|
||||
{"value": item["value"], "label": f"{item['label']}|{item['summary']}"}
|
||||
for item in PLOT_TWIST_STORY_DEPTH_OPTIONS
|
||||
],
|
||||
}
|
||||
],
|
||||
"submitted": False,
|
||||
"answers": {},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _plot_twist_direction_fallback(conversation: CreationConversation) -> list[dict]:
|
||||
"""模型遗漏方向卡工具时的平台兜底,不能只留下「我准备了三个方向」的空话。"""
|
||||
refs = [item for item in (conversation.pinned_refs or []) if isinstance(item, dict)]
|
||||
product = next((str(item.get("name") or "").strip() for item in refs if item.get("type") == "product"), "这款商品")
|
||||
return [
|
||||
{
|
||||
"id": "misunderstanding",
|
||||
"title": "误会翻盘",
|
||||
"conflict": "主角把眼前的麻烦误判成无解,情绪不断升级。",
|
||||
"product_role": f"{product} 作为关键证据或解决工具,在最需要时自然出现。",
|
||||
"reversal": "原来问题的答案一直在眼前,误会被当场化解。",
|
||||
"tone": "轻喜剧、反差感强",
|
||||
},
|
||||
{
|
||||
"id": "last_chance",
|
||||
"title": "最后一次机会",
|
||||
"conflict": "主角已经试过常规办法仍然失败,只剩最后一次选择。",
|
||||
"product_role": f"{product} 承担最后一次可验证的尝试,完整展示正常使用过程。",
|
||||
"reversal": "看似失败的局面被扭转,结果恰好印证核心卖点。",
|
||||
"tone": "紧张后释然、转化更强",
|
||||
},
|
||||
{
|
||||
"id": "foreshadowing",
|
||||
"title": "伏笔回收",
|
||||
"conflict": "开头埋下一个不起眼的细节,人物关系或目标因此受阻。",
|
||||
"product_role": f"{product} 先作为日常细节出现,后半段成为推动结局的关键线索。",
|
||||
"reversal": "前面的细节被回收,观众才发现结局早有铺垫。",
|
||||
"tone": "温暖小反转、故事感更强",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _coerce_plot_twist_directions(raw) -> list[dict]:
|
||||
"""把模型给的三条方向收成可直接渲染的卡片字段。"""
|
||||
if not isinstance(raw, list):
|
||||
return []
|
||||
directions: list[dict] = []
|
||||
for index, item in enumerate(raw[:3], start=1):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
title = str(item.get("title") or item.get("name") or "").strip()
|
||||
conflict = str(item.get("conflict") or item.get("setup") or "").strip()
|
||||
product_role = str(item.get("product_role") or item.get("product") or "").strip()
|
||||
reversal = str(item.get("reversal") or item.get("twist") or "").strip()
|
||||
tone = str(item.get("tone") or item.get("feel") or "").strip()
|
||||
if not all((title, conflict, product_role, reversal)):
|
||||
continue
|
||||
directions.append({
|
||||
"id": str(item.get("id") or f"direction_{index}").strip() or f"direction_{index}",
|
||||
"title": title,
|
||||
"conflict": conflict,
|
||||
"product_role": product_role,
|
||||
"reversal": reversal,
|
||||
"tone": tone or "剧情带货",
|
||||
})
|
||||
return directions if len(directions) == 3 else []
|
||||
|
||||
|
||||
def _append_plot_twist_direction_question(context: "AgentContext", directions: list[dict]) -> CreationMessage:
|
||||
return append_message(
|
||||
context.conversation,
|
||||
role="assistant",
|
||||
kind=CreationMessage.Kind.ELICIT,
|
||||
text="选一个剧情方向,我会按它写成完整方案。",
|
||||
payload={
|
||||
"interaction": "plot_twist_directions",
|
||||
"directions": directions,
|
||||
"fields": [{
|
||||
"key": "story_direction",
|
||||
"label": "三个剧情反转方向",
|
||||
"type": "single",
|
||||
"required": True,
|
||||
"options": [
|
||||
{"value": item["id"], "label": item["title"]}
|
||||
for item in directions
|
||||
],
|
||||
}],
|
||||
"submitted": False,
|
||||
"answers": {},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_video_gate_stage(conversation: CreationConversation) -> str:
|
||||
memory = conversation.memory if isinstance(conversation.memory, dict) else {}
|
||||
stage = str(memory.get("stage") or "clarify").strip()
|
||||
@@ -478,6 +628,17 @@ def text_already_guides(text: str) -> bool:
|
||||
return bool(_GUIDANCE_MARKER_RE.search((text or "").strip()))
|
||||
|
||||
|
||||
_NUMERIC_REPLY_INSTRUCTION_RE = re.compile(
|
||||
r"(?:你|请)?\s*(?:直接)?(?:回复|选择|选)\s*(?:数字|编号)?\s*"
|
||||
r"1\s*(?:[/、,,]\s*2)(?:\s*(?:[/、,,]\s*3))?[^。!?!\n]*(?:[。!?!]|$)"
|
||||
)
|
||||
|
||||
|
||||
def strip_numeric_reply_instruction(text: str) -> str:
|
||||
"""选择器会把 1/2/3 直接做成按钮,正文不再要求用户手输数字。"""
|
||||
return _NUMERIC_REPLY_INSTRUCTION_RE.sub("", text or "").strip()
|
||||
|
||||
|
||||
def default_reply_hint(
|
||||
conversation: CreationConversation | None = None,
|
||||
*,
|
||||
@@ -776,7 +937,7 @@ VIDEO_MODELS = ["Seedance 2.5", "Seedance 2.0", "Seedance 2.0 Fast", "Seedance 2
|
||||
IMAGE_MODELS = ["Seedream5.0", "YQ image2"]
|
||||
RATIOS = ["16:9", "9:16", "4:3", "3:4", "1:1"]
|
||||
RESOLUTIONS = ["480p", "720p", "1080p"]
|
||||
VIDEO_DURATIONS = ["智能时长", "4 秒", "5 秒", "6 秒", "8 秒", "10 秒", "12 秒", "15 秒", "30 秒"]
|
||||
VIDEO_DURATIONS = ["智能时长", "4 秒", "5 秒", "6 秒", "8 秒", "10 秒", "12 秒", "15 秒", "30 秒", "60 秒"]
|
||||
IMAGE_COUNTS = ["1 张", "2 张", "4 张", "8 张"]
|
||||
SESSION_PARAM_KEYS = ("duration", "ratio", "resolution", "video_model", "count")
|
||||
_PARAM_TO_STORED = {"video_model": "model"}
|
||||
@@ -865,6 +1026,9 @@ def apply_session_params(conversation, fields, answers: dict) -> bool:
|
||||
if changed:
|
||||
conversation.params = current
|
||||
conversation.save(update_fields=["params", "updated_at"])
|
||||
if is_plot_twist_conversation(conversation):
|
||||
# 用户在参数卡里改了时长,剧情结构也必须立即跟着切换。
|
||||
set_plot_twist_story_depth(conversation, str(current.get("duration") or ""))
|
||||
return changed
|
||||
|
||||
|
||||
@@ -911,6 +1075,9 @@ def apply_confirm_params(conversation, incoming: dict | None) -> tuple[dict, boo
|
||||
if changed:
|
||||
conversation.params = current
|
||||
conversation.save(update_fields=["params", "updated_at"])
|
||||
if is_plot_twist_conversation(conversation):
|
||||
# 在确认卡改时长也要切换故事契约;随后视图会要求重写旧方案。
|
||||
set_plot_twist_story_depth(conversation, str(current.get("duration") or ""))
|
||||
return snapshot_session_params(conversation), duration_changed
|
||||
|
||||
|
||||
@@ -993,6 +1160,41 @@ def tool_schemas(context: AgentContext, *, allow_plan: bool = True) -> list[dict
|
||||
# 换商品/改参数仍由 wanted_asset_pick / wanted_param_keys 兜底追问。
|
||||
return [t for t in tools if t.get("function", {}).get("name") == "search_library"]
|
||||
if context.is_video:
|
||||
if is_plot_twist_conversation(context.conversation) and active_plot_twist_story_depth(context.conversation):
|
||||
tools.append({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "present_story_directions",
|
||||
"description": (
|
||||
"剧情反转带货预设在用户选定故事深度后,先调用此工具展示 3 个可点击的剧情方向。"
|
||||
"每条都必须有不同的冲突、商品承担的实际作用、反转和情绪;不能只说‘我准备了三个方向’。"
|
||||
"用户点击其一后才可 write_strategy;本工具调用后必须停下来等待选择。"
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directions": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"title": {"type": "string", "description": "短标题,不超过 10 个字"},
|
||||
"conflict": {"type": "string", "description": "人物处境与开场冲突"},
|
||||
"product_role": {"type": "string", "description": "商品如何自然推进剧情"},
|
||||
"reversal": {"type": "string", "description": "最终反转如何回收"},
|
||||
"tone": {"type": "string", "description": "情绪与转化倾向"},
|
||||
},
|
||||
"required": ["title", "conflict", "product_role", "reversal", "tone"],
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["directions"],
|
||||
},
|
||||
},
|
||||
})
|
||||
tools.append({
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -1233,7 +1435,7 @@ def video_duration(params: dict) -> int:
|
||||
digits = "".join(ch for ch in raw if ch.isdigit())
|
||||
if not digits:
|
||||
return SMART_DURATION
|
||||
return max(4, min(int(digits), 30))
|
||||
return max(4, min(int(digits), 60))
|
||||
|
||||
|
||||
def _image_count(params: dict, raw) -> int:
|
||||
@@ -1306,8 +1508,14 @@ def _video_submit_params(context: AgentContext, prompt: str) -> tuple[dict, list
|
||||
它已经排好 角色 → 场景 → 商品 的顺序,那正是出片模型 @图N 的语义依据。"""
|
||||
params = context.conversation.params or {}
|
||||
resolved = resolve_refs(context.team, context.conversation.pinned_refs or [])
|
||||
prompt = apply_video_preset_prompt(context.conversation.preset, prompt)
|
||||
prompt = apply_product_voice_visual_guard(context.conversation, prompt)
|
||||
prompt = apply_product_reality_guard(prompt)
|
||||
prompt = apply_plot_twist_story_contract(
|
||||
context.conversation.preset,
|
||||
active_plot_twist_story_depth(context.conversation),
|
||||
prompt,
|
||||
)
|
||||
submit = {
|
||||
"prompt": prompt,
|
||||
"feature": "omni_create",
|
||||
@@ -1562,7 +1770,9 @@ def build_system_prompt(context: AgentContext, *, allow_plan: bool = True, has_c
|
||||
"",
|
||||
"【每轮必须给引导】",
|
||||
"- 每一轮回复结束时,用户必须知道下一步怎么做:要么调用 ask_user 弹出可选项,"
|
||||
"要么在文字里明确告诉用户该回复什么(例如「请回复:1… 2…」或「直接说想改的卖点」)。",
|
||||
"要么在文字里明确告诉用户该回复什么(例如「直接说想改的卖点」)。",
|
||||
"- 当你给出 2–3 个方向时,必须调用 ask_user 生成可点击选项;正文只解释各方向,"
|
||||
"绝不要求用户回复数字、编号或 1/2/3。",
|
||||
"- 禁止只丢一段解释/分析就结束、让用户不知道该回什么。",
|
||||
"- 用户已经给出可直接执行的图片需求(主体、场景或氛围已足够)时,直接调用 generate_image 进入确认卡;"
|
||||
"不要只描述你准备怎么拍,再让用户继续补一句。",
|
||||
@@ -1601,8 +1811,8 @@ def build_system_prompt(context: AgentContext, *, allow_plan: bool = True, has_c
|
||||
hi = max(lo, min(85, int(dur * 5.7)))
|
||||
lines.append(f"- 当前按约 {dur} 秒出片,口播建议 {lo}–{hi} 字;write_plan 的 voice_chars 填这个区间。")
|
||||
lines.append(
|
||||
"- 视频 4 步闸门(不可同轮连跳):①缺信息 ask_user 停 → ②write_strategy 停等确认 → "
|
||||
"③用户确认后 write_plan 停等确认 → ④方案确认后平台在后台整理完整出片指令,并直接出现积分确认卡。"
|
||||
"- 视频 5 步闸门(不可同轮连跳):①缺信息 ask_user 停 → ②write_strategy 停等确认 → "
|
||||
"③用户确认后 write_plan 停等确认 → ④方案确认后展示完整出片指令并停等确认 → ⑤再显示积分确认卡。"
|
||||
)
|
||||
lines.append(
|
||||
"- 只有用户明确要做片、出方案、改方案、换卖点/剧情时才调用 write_strategy / write_plan;"
|
||||
@@ -1657,12 +1867,36 @@ def build_system_prompt(context: AgentContext, *, allow_plan: bool = True, has_c
|
||||
# 只给名字模型只能靠猜;把这个预设的拍法约束一起给它
|
||||
guidance = preset_guidance(conversation.preset)
|
||||
lines.append(f"\n【创作预设】{conversation.preset}")
|
||||
if context.is_video:
|
||||
lines.append(
|
||||
"预设必须贯穿本次对话:据此判断该补问哪些必要事实和素材、给什么创意选择、"
|
||||
"如何写脚本/分镜、商品如何出现、生成前检查什么;不能把预设当成最后才附加的一行风格词。"
|
||||
)
|
||||
if guidance:
|
||||
lines.append(guidance)
|
||||
lines.append("用户选了这个预设,就按它的拍法来;要偏离得先问过用户。")
|
||||
if is_plot_twist_conversation(conversation):
|
||||
depth = active_plot_twist_story_depth(conversation)
|
||||
if not depth:
|
||||
lines.append("剧情反转带货尚未选择故事深度:必须先调用 ask_user 让用户选 15 秒、30 秒、60 秒或智能推荐;此时禁止给剧情方向、策略或方案。")
|
||||
else:
|
||||
lines.append(plot_twist_story_contract(depth))
|
||||
memory = conversation.memory if isinstance(conversation.memory, dict) else {}
|
||||
selected_direction = str(memory.get("plot_twist_story_direction") or "").strip()
|
||||
if selected_direction:
|
||||
lines.append(f"用户已选剧情方向:{selected_direction}。直接围绕此方向写策略,不要重发方向卡。")
|
||||
else:
|
||||
lines.append(
|
||||
"【强制下一步】现在必须调用 present_story_directions,直接展示 3 张完整剧情方向卡。"
|
||||
"每张卡写清冲突、商品如何推进剧情、反转和情绪;不得只在文字中说‘我准备了三个方向’,"
|
||||
"不得先 write_strategy / write_plan,也不要让用户输入编号。"
|
||||
)
|
||||
workflow_guidance = preset_workflow_guidance(conversation.preset) if context.is_video else ""
|
||||
if workflow_guidance:
|
||||
lines.append(f"【当前预设的工作重点】{workflow_guidance}")
|
||||
delivery_contract = video_preset_delivery_contract(conversation.preset) if context.is_video else ""
|
||||
if delivery_contract:
|
||||
lines.append(f"【当前预设必须贯穿脚本与出片】{delivery_contract}")
|
||||
|
||||
resolved = resolve_refs(context.team, conversation.pinned_refs or [])
|
||||
if resolved.facts:
|
||||
@@ -2039,6 +2273,20 @@ def iter_creation_agent_events(
|
||||
yield {"type": "done"}
|
||||
return
|
||||
|
||||
# 剧情反转预设必须先选故事深度。平台直接落选择卡,不能把这一步交给模型猜,
|
||||
# 否则默认 15 秒会吞掉 30/60 秒该有的人物关系和冲突发展。
|
||||
if is_plot_twist_conversation(conversation) and not active_plot_twist_story_depth(conversation):
|
||||
explicit_depth = plot_twist_story_depth(text)
|
||||
if explicit_depth is not None and explicit_depth["value"] != "smart":
|
||||
set_plot_twist_story_depth(conversation, explicit_depth["value"])
|
||||
else:
|
||||
question = append_plot_twist_story_depth_question(conversation)
|
||||
conversation.agent_status = CreationConversation.AgentStatus.AWAITING_USER
|
||||
conversation.save(update_fields=["agent_status", "updated_at"])
|
||||
yield {"type": "message", "message": _message_payload(question)}
|
||||
yield {"type": "done"}
|
||||
return
|
||||
|
||||
model_config = _prefer_vision_text_model(model_config, conversation.team, conversation.pinned_refs or [])
|
||||
context.model_config = model_config
|
||||
|
||||
@@ -2095,7 +2343,7 @@ def iter_creation_agent_events(
|
||||
elif kind == "tool_call":
|
||||
_merge_tool_call_deltas(tool_buffer, chunk.get("tool_calls"))
|
||||
|
||||
said = "".join(text_buffer).strip()
|
||||
said = strip_numeric_reply_instruction("".join(text_buffer))
|
||||
calls = [tool_buffer[i] for i in sorted(tool_buffer) if tool_buffer[i].get("name")]
|
||||
fallback_fields = None
|
||||
allow_pick = False
|
||||
@@ -2131,6 +2379,28 @@ def iter_creation_agent_events(
|
||||
"asset_types": ["product"],
|
||||
}]
|
||||
|
||||
# 方向卡是剧情反转预设的固定入口。模型偶尔只会说「我准备了三个方向」而忘了调工具,
|
||||
# 此处直接补上可点击卡,不能让用户面对一段空话再自己追问。
|
||||
memory = conversation.memory if isinstance(conversation.memory, dict) else {}
|
||||
needs_plot_twist_directions = (
|
||||
not calls
|
||||
and allow_plan
|
||||
and is_plot_twist_conversation(conversation)
|
||||
and bool(active_plot_twist_story_depth(conversation))
|
||||
and not str(memory.get("plot_twist_story_direction") or "").strip()
|
||||
)
|
||||
if needs_plot_twist_directions:
|
||||
result, _stop = _dispatch_tool(
|
||||
context,
|
||||
"present_story_directions",
|
||||
{"directions": _plot_twist_direction_fallback(conversation)},
|
||||
)
|
||||
for event in result.get("_events", []):
|
||||
yield event
|
||||
if event.get("type") == "message":
|
||||
turn_has_gate = True
|
||||
break
|
||||
|
||||
# ask_user 自己会落一条可追踪的聊天问题。模型同时吐出的过渡文案不再
|
||||
# 另存一条,否则界面会连续出现两遍几乎相同的问题。
|
||||
asks_user = bool(fallback_fields) or any(call.get("name") == "ask_user" for call in calls)
|
||||
@@ -2352,6 +2622,7 @@ def run_creation_agent_turn(
|
||||
|
||||
_TOOL_LABELS = {
|
||||
"ask_user": "向你确认",
|
||||
"present_story_directions": "整理剧情方向",
|
||||
"search_library": "查找素材",
|
||||
"generate_image": "生成图片",
|
||||
"write_strategy": "梳理创作策略",
|
||||
@@ -2476,6 +2747,23 @@ def _dispatch_tool(
|
||||
"_events": [{"type": "message", "message": _message_payload(message)}],
|
||||
}, True
|
||||
|
||||
if name == "present_story_directions":
|
||||
if not is_plot_twist_conversation(context.conversation):
|
||||
return {"payload": {"error": "当前会话不是剧情反转带货预设"}}, False
|
||||
directions = _coerce_plot_twist_directions(args.get("directions"))
|
||||
if not directions:
|
||||
return {
|
||||
"payload": {
|
||||
"error": "必须提供恰好 3 个完整剧情方向,每个都要有标题、冲突、商品作用、反转和情绪。"
|
||||
}
|
||||
}, False
|
||||
message = _append_plot_twist_direction_question(context, directions)
|
||||
set_video_gate_stage(context.conversation, "clarify")
|
||||
return {
|
||||
"payload": {"presented": True, "count": 3},
|
||||
"_events": [{"type": "message", "message": _message_payload(message)}],
|
||||
}, True
|
||||
|
||||
if name == "search_library":
|
||||
return {"payload": _run_search_library(context, args)}, False
|
||||
|
||||
@@ -2515,8 +2803,14 @@ def _dispatch_tool(
|
||||
video_prompt = str(args.get("video_prompt") or "").strip()
|
||||
if not video_prompt:
|
||||
return {"payload": {"error": "video_prompt 不能为空,请把完整出片指令写进去"}}, False
|
||||
video_prompt = apply_video_preset_prompt(context.conversation.preset, video_prompt)
|
||||
video_prompt = apply_product_voice_visual_guard(context.conversation, video_prompt)
|
||||
video_prompt = apply_product_reality_guard(video_prompt)
|
||||
video_prompt = apply_plot_twist_story_contract(
|
||||
context.conversation.preset,
|
||||
active_plot_twist_story_depth(context.conversation),
|
||||
video_prompt,
|
||||
)
|
||||
card = _coerce_plan_card_args(args if isinstance(args, dict) else {})
|
||||
if not card["usp"] or not card["points"]:
|
||||
return {
|
||||
@@ -2548,7 +2842,7 @@ def _dispatch_tool(
|
||||
kind=CreationMessage.Kind.PLAN, payload=plan_payload,
|
||||
)
|
||||
events.append({"type": "message", "message": _message_payload(plan)})
|
||||
# video_prompt 只存后台。用户确认方案后直接进入积分确认,不展示内部 Prompt。
|
||||
# video_prompt 先作为方案产物存档;用户确认方案后会展示指令文件供确认。
|
||||
set_video_gate_stage(
|
||||
context.conversation, "plan", pending_video_prompt=video_prompt
|
||||
)
|
||||
@@ -2565,18 +2859,23 @@ def _dispatch_tool(
|
||||
video_prompt = get_pending_video_prompt(context.conversation)
|
||||
if not video_prompt:
|
||||
return {"payload": {"error": "video_prompt 不能为空,请把完整出片指令写进去"}}, False
|
||||
video_prompt = apply_video_preset_prompt(context.conversation.preset, video_prompt)
|
||||
video_prompt = apply_product_voice_visual_guard(context.conversation, video_prompt)
|
||||
video_prompt = apply_product_reality_guard(video_prompt)
|
||||
set_video_gate_stage(context.conversation, "prompt", pending_video_prompt=video_prompt)
|
||||
confirm = emit_final_confirm_gate(context.conversation, context=context)
|
||||
if confirm is None:
|
||||
return {"payload": {"error": "无法准备出片确认"}}, False
|
||||
video_prompt = apply_plot_twist_story_contract(
|
||||
context.conversation.preset,
|
||||
active_plot_twist_story_depth(context.conversation),
|
||||
video_prompt,
|
||||
)
|
||||
prompt_messages = emit_prompt_gate(context.conversation, video_prompt)
|
||||
if not prompt_messages:
|
||||
return {"payload": {"error": "无法准备出片指令"}}, False
|
||||
events = [
|
||||
{"type": "message", "message": _message_payload(confirm)},
|
||||
{"type": "credits", "estimated": int((confirm.payload or {}).get("estimated_credits") or 0)},
|
||||
{"type": "message", "message": _message_payload(message)}
|
||||
for message in prompt_messages
|
||||
]
|
||||
return {
|
||||
"payload": {"awaiting_confirmation": True, "prepared": True},
|
||||
"payload": {"awaiting_step": "prompt", "prepared": True},
|
||||
"_events": events,
|
||||
}, True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user