from datetime import UTC, datetime from uuid import uuid4 from sqlalchemy.ext.asyncio import AsyncSession from app.core.contracts import RequestContext from app.core.errors import ConflictAgentError, ResourceNotFoundError from app.model.audit import InteractionAudit from app.model.conversation import ConversationFeedback from app.model.platform import HandoverTicket from app.repository.conversation_repository import ConversationRepository from app.service.authorization_service import AuthorizationService class ConversationService: def __init__(self, session: AsyncSession) -> None: self.session = session self.repository = ConversationRepository(session) async def messages( self, session_id: str, context: RequestContext, limit: int ) -> dict[str, object]: rows = await self.repository.messages(session_id, int(context.user_id), limit) return {"data": [{"message_id": str(row.id), "role": row.role, "content": row.content, "created_at": row.created_at.isoformat() + "Z"} for row in rows]} async def feedback( self, message_id: int, context: RequestContext, rating: int, feedback_type: str | None, feedback_content: str | None, ) -> dict[str, object]: await AuthorizationService.require(context, "conversation:feedback") async with self.session.begin(): message = await self.repository.message(message_id, int(context.user_id)) if message is None: raise ResourceNotFoundError("消息不存在") if await self.repository.feedback(message_id, int(context.user_id)) is not None: raise ConflictAgentError("消息已反馈") now = datetime.now(UTC).replace(tzinfo=None) feedback = ConversationFeedback( feedback_no=f"fb-{uuid4().hex[:24]}", session_id=message.session_id, message_id=message_id, customer_id=int(context.user_id), rating=rating, feedback_type=feedback_type, feedback_content=feedback_content, status="open", created_at=now, updated_at=now, ) self.session.add(feedback) self._audit(context, message.session_id, "conversation.feedback_created", {"feedback_no": feedback.feedback_no}) return {"data": {"feedback_no": feedback.feedback_no, "status": feedback.status}} async def handover( self, session_id: str, context: RequestContext, reason_code: str, reason_detail: str | None, ) -> dict[str, object]: await AuthorizationService.require(context, "handover:create") async with self.session.begin(): messages = await self.repository.messages(session_id, int(context.user_id), 1) if not messages: raise ResourceNotFoundError("会话不存在") now = datetime.now(UTC).replace(tzinfo=None) ticket = HandoverTicket( ticket_no=f"ticket-{uuid4().hex[:24]}", session_id=session_id, customer_id=int(context.user_id), source_agent="customer_service", source_message_id=messages[0].id, reason_code=reason_code, reason_detail=reason_detail, status="pending", created_at=now, updated_at=now, ) self.session.add(ticket) self._audit(context, session_id, "conversation.transfer_requested", {"ticket_no": ticket.ticket_no}) return {"data": {"handover_id": ticket.ticket_no, "session_id": session_id, "status": ticket.status, "created_at": now.isoformat() + "Z"}} def _audit( self, context: RequestContext, session_id: str, action: str, detail: dict[str, object] ) -> None: self.session.add(InteractionAudit( actor_type="user", actor_id=int(context.user_id), session_id=session_id, portal=context.portal, action_type=action, detail={**detail, "trace_id": context.trace_id}, created_at=datetime.now(UTC).replace(tzinfo=None), ))