Files
ChatAIBot/services/opening.py
T
2026-06-05 14:57:15 +03:00

192 lines
6.0 KiB
Python

import json
import logging
from services.memory import (
get_history,
get_session,
get_last_assistant_message_id,
update_session_plot_arc,
update_message_choices,
get_quests,
)
from services.rpg_state import apply_narrator_post
from services.personas import get_persona
from services.rpg_facts import facts_to_prompt
from services.rpg_plot import generate_plot_arc, choices_from_narrator
from services.rpg_context import format_narrator_context
from services.rpg_narrator import narrator_post
from services.sd_prompt import generate_sd_prompt
from services.sd_images import run_sd_for_message
logger = logging.getLogger(__name__)
DEFAULT_RPG_SETTINGS = {
"dice": True,
"narrator": True,
"quests": True,
"affinity": True,
"choices": True,
"stats": False,
}
def get_rpg_settings(session: dict) -> dict:
try:
return {**DEFAULT_RPG_SETTINGS, **json.loads(session.get("rpg_settings_json") or "{}")}
except Exception:
return DEFAULT_RPG_SETTINGS
async def resolve_greeting(session_id: str, persona: dict) -> str:
history = await get_history(session_id)
for m in reversed(history):
if m.get("role") == "assistant" and (m.get("content") or "").strip():
return m["content"].strip()
return (persona.get("first_mes") or "").strip()
async def ensure_plot_arc_and_quests(
session_id: str,
persona: dict,
greeting: str,
genre: str,
*,
seed_quests: bool = True,
) -> dict:
session = await get_session(session_id) or {}
arc_json = session.get("plot_arc_json") or "{}"
try:
arc = json.loads(arc_json) if isinstance(arc_json, str) else {}
except Exception:
arc = {}
if arc:
return arc
from services.rpg_locale import infer_rp_language
facts_block = facts_to_prompt(session.get("facts_json", "[]"))
lang = infer_rp_language([{"role": "assistant", "content": greeting}])
arc = await generate_plot_arc(
persona.get("name", "Character"),
persona.get("description", ""),
persona.get("scenario", ""),
greeting,
facts_block=facts_block,
genre=genre,
lang=lang,
recent_context=f"assistant: {greeting}",
)
if not arc:
return {}
await update_session_plot_arc(session_id, json.dumps(arc, ensure_ascii=False))
if seed_quests:
from services.rpg_story import sync_quest_to_current_step
await sync_quest_to_current_step(session_id, arc)
return arc
async def process_opening(session_id: str, persona_id: str, *, rpg: bool) -> dict:
session = await get_session(session_id)
if not session:
raise ValueError("Session not found")
history = await get_history(session_id)
assistant_msgs = [m for m in history if m.get("role") == "assistant"]
if not assistant_msgs:
raise ValueError("No assistant message (first_mes) found")
first_mes_text = assistant_msgs[-1].get("content", "").strip()
if not first_mes_text:
raise ValueError("Empty first_mes")
msg_id = await get_last_assistant_message_id(session_id)
persona = await get_persona(persona_id) or {}
rpg_settings = get_rpg_settings(session)
arc: dict = {}
choices: list = []
status_quo = session.get("status_quo") or ""
outfit_json = session.get("outfit_json") or "[]"
if rpg:
genre = session.get("genre") or "adventure"
arc = await ensure_plot_arc_and_quests(
session_id,
persona,
first_mes_text,
genre,
seed_quests=rpg_settings.get("quests", True),
)
session = await get_session(session_id) or session
ctx_txt = f"assistant: {first_mes_text}"
arc_json = json.dumps(arc, ensure_ascii=False) if arc else ""
facts_block = facts_to_prompt(session.get("facts_json", "[]"))
quests_pre = await get_quests(session_id)
narr_ctx = format_narrator_context(arc, quests_pre, session.get("status_quo") or "")
from services.rpg_locale import infer_rp_language
o_lang = infer_rp_language([{"role": "assistant", "content": first_mes_text}])
post = await narrator_post(
persona.get("name", persona_id),
ctx_txt,
arc_json,
facts_block,
is_opening=True,
extra_context=narr_ctx,
lang=o_lang,
)
if rpg_settings.get("choices", True):
choices = choices_from_narrator(post.get("choices") or [])
from services.rpg_state import apply_narrator_post_with_story
await apply_narrator_post_with_story(
session_id, post, rpg_settings, session, arc=arc
)
session = await get_session(session_id) or session
status_quo = session.get("status_quo") or status_quo
outfit_json = session.get("outfit_json") or outfit_json
quests = await get_quests(session_id)
messages = await get_history(session_id)
bundle = await generate_sd_prompt(
messages,
persona_id,
outfit_json=outfit_json,
scene_json=session.get("scene_json", "{}") if session else "{}",
)
sd_out = await run_sd_for_message(bundle, msg_id) if bundle else {}
updated = await get_session(session_id)
if rpg and msg_id:
from services.memory import save_state_snapshot
await save_state_snapshot(session_id, msg_id)
affinity = updated.get("affinity", 0) if updated else 0
if msg_id and choices:
await update_message_choices(
msg_id, json.dumps(choices, ensure_ascii=False)
)
return {
"plot_arc": arc,
"quests": quests,
"outfit_json": outfit_json,
"status_quo": status_quo,
"choices": choices,
"image_prompt": sd_out.get("image_prompt"),
"image_prompt_alt": sd_out.get("image_prompt_alt"),
"image_path": sd_out.get("image_path"),
"image_path_alt": sd_out.get("image_path_alt"),
"image_error": sd_out.get("image_error"),
"image_error_alt": sd_out.get("image_error_alt"),
"affinity": affinity,
}