This commit is contained in:
Mikan
2026-06-21 09:24:42 +03:00
parent 7cbe8da103
commit c45ab1ddd5
24 changed files with 1438 additions and 148 deletions

View File

@@ -292,6 +292,78 @@ async def stats(
}
# --------------------------------------------------------------------------- #
# Name bank — get/update character name banks per language
# --------------------------------------------------------------------------- #
@router.get("/names/{language}")
async def get_name_bank(
language: str,
db: AsyncSession = Depends(get_db),
_user: User = Depends(require_admin),
) -> dict:
"""Get the character name bank for a language."""
names = await get_setting(db, f"character_names.{language}")
if not isinstance(names, list):
names = []
return {"language": language, "names": names, "count": len(names)}
@router.put("/names/{language}")
async def update_name_bank(
language: str,
body: dict,
db: AsyncSession = Depends(get_db),
_user: User = Depends(require_admin),
) -> dict:
"""Update the character name bank for a language.
Body: {names: ["name1", "name2", ...]}
"""
names = body.get("names", [])
if not isinstance(names, list):
raise HTTPException(400, "names must be an array")
# Validate all entries are strings
cleaned = [str(n).strip() for n in names if str(n).strip()]
await set_setting(db, f"character_names.{language}", cleaned)
return {"language": language, "names": cleaned, "count": len(cleaned)}
@router.post("/names/{language}/add")
async def add_name_to_bank(
language: str,
body: dict,
db: AsyncSession = Depends(get_db),
_user: User = Depends(require_admin),
) -> dict:
"""Add a single name to the bank. Body: {name: "..."}"""
name = body.get("name", "").strip()
if not name:
raise HTTPException(400, "name is required")
names = await get_setting(db, f"character_names.{language}")
if not isinstance(names, list):
names = []
if name not in names:
names.append(name)
await set_setting(db, f"character_names.{language}", names)
return {"language": language, "names": names, "count": len(names)}
@router.delete("/names/{language}/{name}")
async def remove_name_from_bank(
language: str,
name: str,
db: AsyncSession = Depends(get_db),
_user: User = Depends(require_admin),
) -> dict:
"""Remove a name from the bank."""
names = await get_setting(db, f"character_names.{language}")
if not isinstance(names, list):
names = []
names = [n for n in names if n != name]
await set_setting(db, f"character_names.{language}", names)
return {"language": language, "names": names, "count": len(names)}
# --------------------------------------------------------------------------- #
# LLM model list — fetch available models from the LLM provider
# --------------------------------------------------------------------------- #

View File

@@ -13,6 +13,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_current_user, get_settings_dict
from app.config import get_settings
from app.core.llm import LlmClient, MockLlmClient
from app.core.logging import get_logger
from app.db import get_db
@@ -91,6 +92,39 @@ async def get_state(
}
@router.get("/worlds/{world_id}/history")
async def get_history(
world_id: uuid.UUID,
before: int | None = None, # sequence_number to load before (for pagination)
limit: int = 20,
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
) -> dict:
"""Return full chat history with pagination (for scroll-up loading)."""
world = await _load_world(db, world_id, user)
stmt = select(Step).where(
Step.world_id == world.id, Step.deleted_at.is_(None)
)
if before is not None:
stmt = stmt.where(Step.sequence_number < before)
stmt = stmt.order_by(Step.sequence_number.desc()).limit(limit)
rows = (await db.execute(stmt)).scalars().all()
steps = [
{
"id": str(s.id), "sequence_number": s.sequence_number,
"player_action": s.player_action, "scene_text": s.scene_text,
"suggested_actions": s.suggested_actions, "created_at": s.created_at.isoformat(),
}
for s in reversed(rows) # oldest first
]
has_more = len(rows) == limit
return {
"steps": steps,
"has_more": has_more,
"oldest_sequence": steps[0]["sequence_number"] if steps else None,
}
# --------------------------------------------------------------------------- #
# World builder stream (SSE)
# --------------------------------------------------------------------------- #
@@ -107,7 +141,7 @@ async def builder_stream(
preset = (
await db.execute(select(WorldPreset).where(WorldPreset.id == world.preset_id))
).scalar_one_or_none()
emitter = SseEmitter()
emitter = SseEmitter(debug=get_settings().debug)
player_name = (world.environment or {}).get("player", {}).get("name", "Hero")
notes = world.description
llm = _llm_factory(settings)
@@ -152,7 +186,7 @@ async def editor_stream(
settings: dict = Depends(get_settings_dict),
) -> StreamingResponse:
world = await _load_world(db, world_id, user)
emitter = SseEmitter()
emitter = SseEmitter(debug=get_settings().debug)
llm = _llm_factory(settings)
async def run_bg():
@@ -234,7 +268,7 @@ async def iterate_stream(
).scalar_one_or_none()
if step is None:
raise HTTPException(404, "step not found")
emitter = SseEmitter()
emitter = SseEmitter(debug=get_settings().debug)
llm = _llm_factory(settings)
async def run_bg():
@@ -349,7 +383,7 @@ async def intro_stream(
settings: dict = Depends(get_settings_dict),
) -> StreamingResponse:
world = await _load_world(db, world_id, user)
emitter = SseEmitter()
emitter = SseEmitter(debug=get_settings().debug)
llm = _llm_factory(settings)
async def run_bg():