zioinfo-mail/_archive/messenger/routers/ws_relay.py
DESKTOP-TKLFCPR\ython 28d3ba4836 refactor(cleanup): commit folder reorganization - scripts/, _archive/, docs/ restructure
- Move backend/frontend/messenger/ old paths to _archive/
- Reorganize scripts into scripts/deploy, check, push, setup, misc
- Move docs (pptx, docx) to docs/
- Add .claude agents and skills for fullstack/folder-cleanup harness
- workspace/ projects remain intact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:43:09 +09:00

130 lines
4.4 KiB
Python

import asyncio
import json
from datetime import datetime
from typing import Dict, List
from uuid import uuid4
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from core.bot import (
ROOMS,
check_proactive_context,
get_bot_response,
message_store,
store_message,
)
from core.chatbot import get_chatbot_response
router = APIRouter()
# room_id → list of connected WebSocket clients
room_channels: Dict[str, List[WebSocket]] = {}
def _new_msg_id() -> str:
return f"MSG-{datetime.now().strftime('%Y%m%d')}-{str(uuid4())[:8].upper()}"
async def broadcast(room_id: str, payload: dict) -> None:
dead: List[WebSocket] = []
for ws in room_channels.get(room_id, []):
try:
await ws.send_text(json.dumps(payload, ensure_ascii=False))
except Exception:
dead.append(ws)
for ws in dead:
room_channels[room_id].remove(ws)
def _make_bot_msg(room_id: str, reply: dict) -> dict:
return {
"message_id": _new_msg_id(),
"timestamp": datetime.now().isoformat(),
"room_id": room_id,
"sender": "GUARDiA-Bot",
"sender_type": "BOT",
"msg_type": "CHAT",
"content": reply.get("content", ""),
"is_widget": reply.get("is_widget", False),
"interactive_action": reply.get("interactive_action"),
}
async def _send_typing(room_id: str, show: bool) -> None:
await broadcast(room_id, {"type": "bot_typing", "show": show, "room_id": room_id})
@router.websocket("/ws/chat/{room_id}/{client_id}")
async def chat_endpoint(ws: WebSocket, room_id: str, client_id: str):
await ws.accept()
room_channels.setdefault(room_id, []).append(ws)
history = message_store.get(room_id, [])[-50:]
await ws.send_text(json.dumps(
{"type": "init", "messages": history, "rooms": ROOMS},
ensure_ascii=False,
))
try:
while True:
raw = await ws.receive_text()
data = json.loads(raw)
text = data.get("content", "").strip()
if not text:
continue
# 사용자 메시지 저장 + 브로드캐스트
user_msg = {
"message_id": _new_msg_id(),
"timestamp": datetime.now().isoformat(),
"room_id": room_id,
"sender": client_id,
"sender_type": "HUMAN",
"msg_type": "CHAT",
"content": text,
"is_widget": False,
"interactive_action": None,
}
store_message(room_id, user_msg)
await broadcast(room_id, user_msg)
# ── chatbot 전용 채널 ───────────────────────────────
if room_id == "chatbot":
await _send_typing(room_id, True)
try:
reply_text = await asyncio.wait_for(
get_chatbot_response(client_id, text),
timeout=10.0,
)
except asyncio.TimeoutError:
reply_text = "응답 시간이 초과되었습니다. 잠시 후 다시 시도해주세요."
finally:
await _send_typing(room_id, False)
bot_msg = _make_bot_msg(room_id, {"content": reply_text, "is_widget": False})
store_message(room_id, bot_msg)
await broadcast(room_id, bot_msg)
continue
# ── 일반 채널: @bot 명령 처리 ──────────────────────
bot_reply = get_bot_response(text)
if bot_reply:
await asyncio.sleep(0.4)
bot_msg = _make_bot_msg(room_id, bot_reply)
store_message(room_id, bot_msg)
await broadcast(room_id, bot_msg)
continue
# 선제적 맥락 분석
proactive = check_proactive_context(text)
if proactive:
await asyncio.sleep(1.2)
pro_msg = _make_bot_msg(room_id, proactive)
store_message(room_id, pro_msg)
await broadcast(room_id, pro_msg)
except WebSocketDisconnect:
lst = room_channels.get(room_id, [])
if ws in lst:
lst.remove(ws)