81 lines
4.0 KiB
Python
81 lines
4.0 KiB
Python
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),
|
|
))
|