Files
matrix-show/backend/app/sockets/handlers.py
T
2026-06-04 17:59:53 +08:00

170 lines
6.1 KiB
Python

import asyncio
import logging
from datetime import datetime
import socketio
from app.models.database import async_session_maker
from app.models.orm import Character, Clue, Message, Vote
from app.models.schemas import GamePhase
from app.services.agent_scheduler import agent_scheduler
from app.services.game_state_manager import game_state_manager
from sqlalchemy import select
logger = logging.getLogger(__name__)
sio = socketio.AsyncServer(
async_mode="asgi",
cors_allowed_origins="*",
logger=False,
)
connected_clients: dict[str, dict] = {}
_tracked_tasks: set[asyncio.Task] = set()
@sio.event
async def connect(sid, environ, auth):
connected_clients[sid] = {"sid": sid, "role": "viewer"}
await sio.emit("system", {"message": "Connected to Hermes Live Show"}, to=sid)
@sio.event
async def disconnect(sid):
connected_clients.pop(sid, None)
@sio.event
async def set_role(sid, data):
if sid in connected_clients:
connected_clients[sid]["role"] = data.get("role", "viewer")
@sio.event
async def send_message(sid, data):
character_id = data.get("character_id")
content = data.get("content", "")
msg_type = data.get("msg_type", "character")
game_phase = data.get("game_phase", "")
target_character_id = data.get("target_character_id")
if async_session_maker:
async with async_session_maker() as session:
msg = Message(
character_id=character_id,
game_phase=game_phase,
msg_type=msg_type,
content=content,
target_character_id=target_character_id,
)
session.add(msg)
await session.commit()
await sio.emit("new_message", {
"character_id": character_id,
"content": content,
"msg_type": msg_type,
"game_phase": game_phase,
"target_character_id": target_character_id,
})
@sio.event
async def dm_command(sid, data):
command = data.get("command", "")
args = data.get("args", {})
if async_session_maker:
async with async_session_maker() as session:
if command == "next_phase":
state = await game_state_manager.next_phase(session)
await sio.emit("state_change", {
"phase": state.current_phase,
"progress": game_state_manager.get_progress_percent(state),
})
elif command == "prev_phase":
state = await game_state_manager.prev_phase(session)
await sio.emit("state_change", {
"phase": state.current_phase,
"progress": game_state_manager.get_progress_percent(state),
})
elif command == "unlock_clue":
clue_id = args.get("clue_id")
character_id = args.get("character_id")
if clue_id and character_id:
result = await session.execute(select(Clue).where(Clue.id == clue_id))
clue = result.scalars().first()
if clue:
clue.is_unlocked = True
clue.unlocked_by = character_id
clue.unlocked_at = datetime.utcnow()
await session.commit()
await sio.emit("clue_unlocked", {
"clue_id": clue.id,
"clue_name": clue.name,
"content": clue.content,
"unlocked_by": character_id,
})
elif command == "start_auto":
state = await game_state_manager.get_state(session)
script_id = args.get("script_id") or (state.active_script_id if state else None)
if script_id:
interval = float(args.get("interval", 8.0))
rounds = int(args.get("rounds", 1))
async def emit(event, data):
await sio.emit(event, data)
task = asyncio.create_task(
agent_scheduler.run_auto_speaking(session, script_id, interval, rounds, emit)
)
_tracked_tasks.add(task)
task.add_done_callback(_tracked_tasks.discard)
await sio.emit("system", {"message": "Auto speaking started"})
elif command == "stop_auto":
agent_scheduler.stop()
await sio.emit("system", {"message": "Auto speaking stopped"})
elif command == "trigger_vote":
state = await game_state_manager.get_state(session)
script_id = args.get("script_id") or (state.active_script_id if state else None)
if script_id:
async def emit(event, data):
await sio.emit(event, data)
votes = await agent_scheduler.trigger_voting(session, script_id, emit)
current_state = await game_state_manager.get_or_create_state(session)
round_num = current_state.round_number if hasattr(current_state, 'round_number') else 1
for v in votes:
session.add(Vote(
voter_id=v["voter_id"],
target_id=v["target_id"],
round_number=round_num,
reason=v.get("reason", ""),
))
await session.commit()
await sio.emit("voting_results", {"votes": votes})
@sio.event
async def request_state(sid, data):
if async_session_maker:
async with async_session_maker() as session:
state = await game_state_manager.get_or_create_state(session)
await sio.emit("state_change", {
"phase": state.current_phase,
"progress": game_state_manager.get_progress_percent(state),
"is_running": state.is_running,
"is_paused": state.is_paused,
}, to=sid)
def create_socket_app(app):
return socketio.ASGIApp(sio, other_app=app)