init
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.database import get_session
|
||||
from app.models.orm import Character
|
||||
from app.models.schemas import (
|
||||
CharacterResponse,
|
||||
CharacterCreate,
|
||||
CharacterStatus,
|
||||
CharacterSpeakRequest,
|
||||
SoulUpdateRequest,
|
||||
CharacterGenerateRequest,
|
||||
)
|
||||
from app.services.character_generator import character_generator
|
||||
from app.services.hermes_client import hermes_client
|
||||
from app.services.soul_generator import soul_generator
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=list[CharacterResponse])
|
||||
async def list_characters(session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Character))
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.get("/{character_id}", response_model=CharacterResponse)
|
||||
async def get_character(character_id: str, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Character).where(Character.id == character_id))
|
||||
character = result.scalars().first()
|
||||
if not character:
|
||||
raise HTTPException(status_code=404, detail="Character not found")
|
||||
return character
|
||||
|
||||
|
||||
@router.put("/{character_id}/soul")
|
||||
async def update_character_soul(
|
||||
character_id: str,
|
||||
req: SoulUpdateRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(Character).where(Character.id == character_id))
|
||||
character = result.scalars().first()
|
||||
if not character:
|
||||
raise HTTPException(status_code=404, detail="Character not found")
|
||||
|
||||
previous = character.hermes_profile
|
||||
character.soul_md = req.soul_md
|
||||
character.knowledge_base = req.knowledge_base
|
||||
|
||||
await hermes_client.create_profile(previous, req.soul_md)
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(character)
|
||||
return {"ok": True, "character_id": character_id}
|
||||
|
||||
|
||||
@router.post("/{character_id}/speak")
|
||||
async def character_speak(
|
||||
character_id: str,
|
||||
req: CharacterSpeakRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(Character).where(Character.id == character_id))
|
||||
character = result.scalars().first()
|
||||
if not character:
|
||||
raise HTTPException(status_code=404, detail="Character not found")
|
||||
|
||||
prompt = req.prompt or "根据当前剧本进度自然发言"
|
||||
response = await hermes_client.chat(character.hermes_profile, prompt)
|
||||
|
||||
return {
|
||||
"character_id": character_id,
|
||||
"character_name": character.name,
|
||||
"response": response,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{character_id}/profile")
|
||||
async def create_character_profile(
|
||||
character_id: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(Character).where(Character.id == character_id))
|
||||
character = result.scalars().first()
|
||||
if not character:
|
||||
raise HTTPException(status_code=404, detail="Character not found")
|
||||
|
||||
success = await character_generator.create_profile(character)
|
||||
await session.commit()
|
||||
return {"ok": success, "character_id": character_id}
|
||||
|
||||
|
||||
@router.post("/generate", response_model=list[CharacterResponse])
|
||||
async def generate_characters(
|
||||
req: CharacterGenerateRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(Character).where(Character.script_id == req.script_id))
|
||||
existing = list(result.scalars().all())
|
||||
for char in existing:
|
||||
await session.delete(char)
|
||||
await session.commit()
|
||||
|
||||
character_data = [{"name": name} for name in req.character_names]
|
||||
characters = await character_generator.generate_characters(
|
||||
session, req.script_id, character_data
|
||||
)
|
||||
return characters
|
||||
|
||||
|
||||
@router.post("/{character_id}/status")
|
||||
async def update_character_status(
|
||||
character_id: str,
|
||||
status: CharacterStatus,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(Character).where(Character.id == character_id))
|
||||
character = result.scalars().first()
|
||||
if not character:
|
||||
raise HTTPException(status_code=404, detail="Character not found")
|
||||
|
||||
character.status = status
|
||||
await session.commit()
|
||||
await session.refresh(character)
|
||||
return {"ok": True, "status": character.status.value}
|
||||
|
||||
|
||||
@router.delete("/{character_id}")
|
||||
async def delete_character(character_id: str, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Character).where(Character.id == character_id))
|
||||
character = result.scalars().first()
|
||||
if not character:
|
||||
raise HTTPException(status_code=404, detail="Character not found")
|
||||
|
||||
await character_generator.delete_profile(character)
|
||||
await session.delete(character)
|
||||
await session.commit()
|
||||
return {"ok": True}
|
||||
@@ -0,0 +1,78 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.database import get_session
|
||||
from app.models.orm import Message, Character
|
||||
from app.models.schemas import MessageCreate, MessageResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/messages", response_model=list[MessageResponse])
|
||||
async def list_messages(
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(
|
||||
select(Message).order_by(Message.created_at.asc())
|
||||
)
|
||||
messages = list(result.scalars().all())
|
||||
return [
|
||||
MessageResponse(
|
||||
id=m.id,
|
||||
session_id=m.session_id,
|
||||
character_id=m.character_id,
|
||||
game_phase=m.game_phase,
|
||||
msg_type=m.msg_type,
|
||||
content=m.content,
|
||||
target_character_id=m.target_character_id,
|
||||
clue_id=m.clue_id,
|
||||
metadata=m.metadata or {},
|
||||
created_at=m.created_at,
|
||||
character_name=m.character.name if m.character else None,
|
||||
character_role=m.character.role if m.character else None,
|
||||
)
|
||||
for m in messages
|
||||
]
|
||||
|
||||
|
||||
@router.post("/messages", response_model=MessageResponse)
|
||||
async def create_message(
|
||||
req: MessageCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
message = Message(
|
||||
session_id=req.session_id,
|
||||
character_id=req.character_id,
|
||||
game_phase=req.game_phase,
|
||||
msg_type=req.msg_type,
|
||||
content=req.content,
|
||||
target_character_id=req.target_character_id,
|
||||
)
|
||||
session.add(message)
|
||||
await session.commit()
|
||||
await session.refresh(message)
|
||||
|
||||
char_name = None
|
||||
char_role = None
|
||||
if message.character_id:
|
||||
result = await session.execute(select(Character).where(Character.id == message.character_id))
|
||||
char = result.scalars().first()
|
||||
if char:
|
||||
char_name = char.name
|
||||
char_role = char.role
|
||||
|
||||
return MessageResponse(
|
||||
id=message.id,
|
||||
session_id=message.session_id,
|
||||
character_id=message.character_id,
|
||||
game_phase=message.game_phase,
|
||||
msg_type=message.msg_type,
|
||||
content=message.content,
|
||||
target_character_id=message.target_character_id,
|
||||
clue_id=message.clue_id,
|
||||
metadata=message.metadata or {},
|
||||
created_at=message.created_at,
|
||||
character_name=char_name,
|
||||
character_role=char_role,
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.database import get_session
|
||||
from app.models.orm import Clue, Vote, Character
|
||||
from app.models.schemas import ClueCreate, ClueResponse, ClueUnlockRequest, VoteCreate, VoteResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/{script_id}/clues", response_model=list[ClueResponse])
|
||||
async def list_clues(script_id: str, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Clue).where(Clue.script_id == script_id))
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.post("/{script_id}/clues", response_model=ClueResponse)
|
||||
async def create_clue(script_id: str, req: ClueCreate, session: AsyncSession = Depends(get_session)):
|
||||
clue = Clue(
|
||||
script_id=script_id,
|
||||
name=req.name,
|
||||
content=req.content,
|
||||
clue_type=req.clue_type,
|
||||
owner_id=req.owner_id,
|
||||
phase=req.phase,
|
||||
visibility=req.visibility,
|
||||
visible_to=req.visible_to,
|
||||
)
|
||||
session.add(clue)
|
||||
await session.commit()
|
||||
await session.refresh(clue)
|
||||
return clue
|
||||
|
||||
|
||||
@router.post("/clues/{clue_id}/unlock")
|
||||
async def unlock_clue(
|
||||
clue_id: str,
|
||||
req: ClueUnlockRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(Clue).where(Clue.id == clue_id))
|
||||
clue = result.scalars().first()
|
||||
if not clue:
|
||||
raise HTTPException(status_code=404, detail="Clue not found")
|
||||
|
||||
from datetime import datetime
|
||||
clue.is_unlocked = True
|
||||
clue.unlocked_by = req.character_id
|
||||
clue.unlocked_at = datetime.utcnow()
|
||||
await session.commit()
|
||||
await session.refresh(clue)
|
||||
return {"ok": True, "clue": ClueResponse.model_validate(clue)}
|
||||
|
||||
|
||||
@router.delete("/clues/{clue_id}")
|
||||
async def delete_clue(clue_id: str, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Clue).where(Clue.id == clue_id))
|
||||
clue = result.scalars().first()
|
||||
if not clue:
|
||||
raise HTTPException(status_code=404, detail="Clue not found")
|
||||
await session.delete(clue)
|
||||
await session.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/votes", response_model=list[VoteResponse])
|
||||
async def list_votes(round_number: int = 1, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Vote).where(Vote.round_number == round_number))
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.post("/votes", response_model=VoteResponse)
|
||||
async def create_vote(req: VoteCreate, session: AsyncSession = Depends(get_session)):
|
||||
vote = Vote(
|
||||
voter_id=req.voter_id,
|
||||
target_id=req.target_id,
|
||||
reason=req.reason,
|
||||
)
|
||||
session.add(vote)
|
||||
await session.commit()
|
||||
await session.refresh(vote)
|
||||
return vote
|
||||
@@ -0,0 +1,78 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Body
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.models.database import get_session
|
||||
from app.models.schemas import GameStateResponse, DmSpeakRequest
|
||||
from app.services.game_state_manager import game_state_manager
|
||||
from app.services.agent_scheduler import agent_scheduler
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class StartGameRequest(BaseModel):
|
||||
script_id: str
|
||||
|
||||
|
||||
class SetSpeakerRequest(BaseModel):
|
||||
character_id: str
|
||||
|
||||
|
||||
@router.get("/state", response_model=GameStateResponse)
|
||||
async def get_state(session: AsyncSession = Depends(get_session)):
|
||||
state = await game_state_manager.get_or_create_state(session)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/start", response_model=GameStateResponse)
|
||||
async def start_game(
|
||||
req: StartGameRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
state = await game_state_manager.start_game(session, req.script_id)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/pause", response_model=GameStateResponse)
|
||||
async def pause_game(session: AsyncSession = Depends(get_session)):
|
||||
state = await game_state_manager.pause_game(session)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/resume", response_model=GameStateResponse)
|
||||
async def resume_game(session: AsyncSession = Depends(get_session)):
|
||||
state = await game_state_manager.resume_game(session)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/phase/next", response_model=GameStateResponse)
|
||||
async def phase_next(session: AsyncSession = Depends(get_session)):
|
||||
state = await game_state_manager.next_phase(session)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/phase/prev", response_model=GameStateResponse)
|
||||
async def phase_prev(session: AsyncSession = Depends(get_session)):
|
||||
state = await game_state_manager.prev_phase(session)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/dm/speak")
|
||||
async def dm_speak(req: DmSpeakRequest):
|
||||
result = await agent_scheduler.dm_speak(req.content)
|
||||
return {"content": result, "msg_type": "dm", "phase": req.phase}
|
||||
|
||||
|
||||
@router.post("/speaker/set", response_model=GameStateResponse)
|
||||
async def set_speaker(
|
||||
req: SetSpeakerRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
state = await game_state_manager.set_speaker(session, req.character_id)
|
||||
return game_state_manager.to_response(state)
|
||||
|
||||
|
||||
@router.post("/reset", response_model=GameStateResponse)
|
||||
async def reset_game(session: AsyncSession = Depends(get_session)):
|
||||
state = await game_state_manager.reset(session)
|
||||
return game_state_manager.to_response(state)
|
||||
@@ -0,0 +1,93 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.database import get_session
|
||||
from app.models.orm import Script
|
||||
from app.models.schemas import ScriptUploadRequest, ScriptParsePreview, ScriptImportRequest, ScriptResponse
|
||||
from app.services.script_parser import script_parser
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/upload", response_model=ScriptParsePreview)
|
||||
async def upload_script(
|
||||
req: ScriptUploadRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
parsed = await script_parser.parse(req.content, req.file_type)
|
||||
return ScriptParsePreview(
|
||||
title=parsed["title"],
|
||||
background=parsed["background"],
|
||||
characters=parsed["characters"],
|
||||
clues=parsed["clues"],
|
||||
phases=parsed["phases"],
|
||||
)
|
||||
|
||||
|
||||
@router.post("/import", response_model=ScriptResponse)
|
||||
async def import_script(
|
||||
req: ScriptImportRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
script = Script(
|
||||
title=req.title,
|
||||
background=req.background,
|
||||
raw_content="",
|
||||
parsed_data={
|
||||
"characters": req.characters,
|
||||
"clues": req.clues,
|
||||
"phases": req.phases,
|
||||
},
|
||||
character_count=len(req.characters),
|
||||
)
|
||||
session.add(script)
|
||||
await session.commit()
|
||||
await session.refresh(script)
|
||||
return script
|
||||
|
||||
|
||||
@router.get("/preview")
|
||||
async def preview_scripts(session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(
|
||||
select(Script).order_by(Script.created_at.desc())
|
||||
)
|
||||
scripts = list(result.scalars().all())
|
||||
return [
|
||||
{
|
||||
"id": s.id,
|
||||
"title": s.title,
|
||||
"background": s.background,
|
||||
"character_count": s.character_count,
|
||||
"parsed_data": s.parsed_data,
|
||||
}
|
||||
for s in scripts
|
||||
]
|
||||
|
||||
|
||||
@router.get("", response_model=list[ScriptResponse])
|
||||
async def list_scripts(session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(
|
||||
select(Script).order_by(Script.created_at.desc())
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.get("/{script_id}", response_model=ScriptResponse)
|
||||
async def get_script(script_id: str, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Script).where(Script.id == script_id))
|
||||
script = result.scalars().first()
|
||||
if not script:
|
||||
raise HTTPException(status_code=404, detail="Script not found")
|
||||
return script
|
||||
|
||||
|
||||
@router.delete("/{script_id}")
|
||||
async def delete_script(script_id: str, session: AsyncSession = Depends(get_session)):
|
||||
result = await session.execute(select(Script).where(Script.id == script_id))
|
||||
script = result.scalars().first()
|
||||
if not script:
|
||||
raise HTTPException(status_code=404, detail="Script not found")
|
||||
await session.delete(script)
|
||||
await session.commit()
|
||||
return {"ok": True}
|
||||
Reference in New Issue
Block a user