Fixed SD RPG
This commit is contained in:
+195
-2
@@ -1,8 +1,11 @@
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from services.memory import (
|
||||
get_all_sessions,
|
||||
get_session,
|
||||
get_or_create_session,
|
||||
get_history,
|
||||
delete_session,
|
||||
update_session_title,
|
||||
update_session_persona,
|
||||
@@ -14,10 +17,27 @@ from services.memory import (
|
||||
update_session_genre,
|
||||
update_session_rpg_settings,
|
||||
get_quests,
|
||||
update_quest_by_id,
|
||||
set_session_affinity,
|
||||
update_session_narrative_stats,
|
||||
update_session_outfit,
|
||||
update_session_scene,
|
||||
update_session_plot_arc,
|
||||
get_last_message_preview,
|
||||
fork_session,
|
||||
)
|
||||
from models.schemas import ForkSessionRequest
|
||||
from models.schemas import (
|
||||
ForkSessionRequest,
|
||||
RebindPersonaRequest,
|
||||
QuestStatusPatch,
|
||||
RpgStateDebugPatch,
|
||||
SessionContextPatch,
|
||||
)
|
||||
from services.rpg_plot import reconcile_plot_arc
|
||||
from services.rpg_state import parse_stats_json
|
||||
from services.chat_prompt import get_system_prompt
|
||||
from services.memory import rebind_session_persona
|
||||
from services.personas import get_persona
|
||||
|
||||
router = APIRouter(prefix="/sessions", tags=["sessions"])
|
||||
|
||||
@@ -35,9 +55,149 @@ async def list_sessions():
|
||||
|
||||
@router.get("/{session_id}/quests")
|
||||
async def list_quests(session_id: str):
|
||||
session = await get_session(session_id)
|
||||
if session and session.get("rpg_enabled"):
|
||||
persona = await get_persona(session.get("persona_id") or "default") or {}
|
||||
await reconcile_plot_arc(
|
||||
session_id,
|
||||
persona_name=persona.get("name", session.get("persona_id") or "Character"),
|
||||
recent_context=(session.get("status_quo") or "")[:2000],
|
||||
genre=session.get("genre") or "adventure",
|
||||
)
|
||||
return await get_quests(session_id)
|
||||
|
||||
|
||||
@router.patch("/{session_id}/context")
|
||||
async def patch_session_context(session_id: str, body: SessionContextPatch):
|
||||
"""Live-edit session context (outfit, scene, plot, facts, status quo)."""
|
||||
from services.outfit_tags import parse_and_normalize_outfit_json
|
||||
|
||||
session = await get_session(session_id)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Сессия не найдена")
|
||||
|
||||
if body.status_quo is not None:
|
||||
await update_session_status_quo(session_id, body.status_quo)
|
||||
if body.global_plot is not None:
|
||||
await update_session_global_plot(session_id, body.global_plot)
|
||||
if body.outfit_json is not None:
|
||||
try:
|
||||
normalized = parse_and_normalize_outfit_json(body.outfit_json)
|
||||
json.loads(normalized)
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"outfit_json: {e}") from e
|
||||
await update_session_outfit(session_id, normalized)
|
||||
if body.scene_json is not None:
|
||||
try:
|
||||
json.loads(body.scene_json or "{}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"scene_json: {e}") from e
|
||||
await update_session_scene(session_id, body.scene_json)
|
||||
if body.facts_json is not None:
|
||||
from services.rpg_facts import (
|
||||
parse_facts_list,
|
||||
facts_list_to_json,
|
||||
dedupe_facts_fuzzy,
|
||||
compress_facts,
|
||||
FACTS_DEDUP_THRESHOLD,
|
||||
FACTS_COMPRESS_TARGET,
|
||||
)
|
||||
|
||||
try:
|
||||
facts = dedupe_facts_fuzzy(parse_facts_list(body.facts_json))
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"facts_json: {e}") from e
|
||||
if len(facts) > FACTS_DEDUP_THRESHOLD:
|
||||
facts = await compress_facts(
|
||||
facts,
|
||||
status_quo=(session.get("status_quo") or ""),
|
||||
scene_context=session.get("scene_json") or "{}",
|
||||
target=FACTS_COMPRESS_TARGET,
|
||||
)
|
||||
facts = dedupe_facts_fuzzy(facts)
|
||||
normalized = facts_list_to_json(facts)
|
||||
await update_session_facts(session_id, normalized)
|
||||
if body.plot_arc_json is not None:
|
||||
try:
|
||||
json.loads(body.plot_arc_json or "{}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"plot_arc_json: {e}") from e
|
||||
await update_session_plot_arc(session_id, body.plot_arc_json)
|
||||
if body.affinity is not None:
|
||||
await set_session_affinity(session_id, body.affinity)
|
||||
stats_changed = any(
|
||||
getattr(body, k) is not None for k in ("lust", "stamina", "tension")
|
||||
)
|
||||
if stats_changed:
|
||||
stats = parse_stats_json(session.get("narrative_stats_json"))
|
||||
for key in ("lust", "stamina", "tension"):
|
||||
val = getattr(body, key, None)
|
||||
if val is not None:
|
||||
stats[key] = max(0, min(10, int(val)))
|
||||
await update_session_narrative_stats(
|
||||
session_id, json.dumps(stats, ensure_ascii=False)
|
||||
)
|
||||
|
||||
updated = await get_session(session_id) or session
|
||||
return {
|
||||
"status": "updated",
|
||||
"outfit_json": updated.get("outfit_json", "[]"),
|
||||
"scene_json": updated.get("scene_json", "{}"),
|
||||
"affinity": updated.get("affinity", 0),
|
||||
"narrative_stats": parse_stats_json(updated.get("narrative_stats_json")),
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/{session_id}/rpg-state")
|
||||
async def patch_rpg_state(session_id: str, body: RpgStateDebugPatch):
|
||||
"""Debug: set affinity and/or narrative stats (lust/stamina/tension 0–10)."""
|
||||
session = await get_session(session_id)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Сессия не найдена")
|
||||
affinity = session.get("affinity", 0)
|
||||
if body.affinity is not None:
|
||||
affinity = await set_session_affinity(session_id, body.affinity)
|
||||
stats = parse_stats_json(session.get("narrative_stats_json"))
|
||||
changed_stats = False
|
||||
for key in ("lust", "stamina", "tension"):
|
||||
val = getattr(body, key, None)
|
||||
if val is not None:
|
||||
stats[key] = max(0, min(10, int(val)))
|
||||
changed_stats = True
|
||||
if changed_stats:
|
||||
import json as _json
|
||||
|
||||
await update_session_narrative_stats(
|
||||
session_id, _json.dumps(stats, ensure_ascii=False)
|
||||
)
|
||||
return {
|
||||
"affinity": affinity,
|
||||
"narrative_stats": stats,
|
||||
"target": "current_player",
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/{session_id}/quests/{quest_id}")
|
||||
async def patch_quest(session_id: str, quest_id: int, body: QuestStatusPatch):
|
||||
status = body.status.strip()
|
||||
if status not in ("active", "done", "failed"):
|
||||
raise HTTPException(status_code=400, detail="status must be active, done, or failed")
|
||||
ok = await update_quest_by_id(quest_id, session_id, status)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=404, detail="Quest not found")
|
||||
if status in ("done", "failed"):
|
||||
session = await get_session(session_id)
|
||||
if session and session.get("rpg_enabled"):
|
||||
persona = await get_persona(session.get("persona_id") or "default") or {}
|
||||
await reconcile_plot_arc(
|
||||
session_id,
|
||||
persona_name=persona.get("name", session.get("persona_id") or "Character"),
|
||||
recent_context=(session.get("status_quo") or "")[:2000],
|
||||
genre=session.get("genre") or "adventure",
|
||||
)
|
||||
return {"status": "updated", "quest_id": quest_id, "new_status": status}
|
||||
|
||||
|
||||
@router.get("/{session_id}")
|
||||
async def get_session_route(session_id: str):
|
||||
s = await get_session(session_id)
|
||||
@@ -46,9 +206,42 @@ async def get_session_route(session_id: str):
|
||||
return s
|
||||
|
||||
|
||||
@router.post("/{session_id}/rebind-persona")
|
||||
async def rebind_persona(session_id: str, body: RebindPersonaRequest):
|
||||
session = await get_session(session_id)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Сессия не найдена")
|
||||
persona = await get_persona(body.persona_id)
|
||||
if not persona:
|
||||
raise HTTPException(status_code=400, detail="Персонаж не найден")
|
||||
|
||||
hist = [] if body.clear_history else await get_history(session_id)
|
||||
static = await get_system_prompt(body.persona_id, hist, "")
|
||||
first_mes = (persona.get("first_mes") or "").strip() if body.clear_history else None
|
||||
|
||||
try:
|
||||
await rebind_session_persona(
|
||||
session_id,
|
||||
body.persona_id,
|
||||
clear_history=body.clear_history,
|
||||
static_prompt=static,
|
||||
first_mes=first_mes or None,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
|
||||
return {
|
||||
"persona_id": body.persona_id,
|
||||
"persona_name": persona.get("name", body.persona_id),
|
||||
"system_prompt_preview": static[:500],
|
||||
"clear_history": body.clear_history,
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/{session_id}")
|
||||
async def patch_session(session_id: str, data: dict):
|
||||
await get_or_create_session(session_id, data.get("persona_id", "default"))
|
||||
create_pid = data.get("persona_id") if "persona_id" in data else None
|
||||
await get_or_create_session(session_id, create_pid)
|
||||
if "title" in data:
|
||||
await update_session_title(session_id, data["title"])
|
||||
if "persona_id" in data:
|
||||
|
||||
Reference in New Issue
Block a user