Download app/api/conversations.py from nvikou/ReplyFlow: direct link, hf CLI and curl.
- Browser
- Download file 5.02 kB
-
https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/api/conversations.py
- Command line
-
hf download hf://spaces/nvikou/ReplyFlow/app/api/conversations.py
-
curl -L -o conversations.py https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/api/conversations.py
5.02 kB
| """Conversation history API.""" | |
| from __future__ import annotations | |
| import csv | |
| import io | |
| from typing import Any | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from fastapi.responses import StreamingResponse | |
| from sqlalchemy import func, select | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from sqlalchemy.orm import selectinload | |
| from app.db import get_db | |
| from app.models import Conversation, Lead, Message | |
| from app.schemas import ( | |
| ConversationDetail, | |
| ConversationSummary, | |
| MessageOut, | |
| ) | |
| from app.services.leads import lead_to_dict | |
| router = APIRouter( | |
| prefix="/api/conversations", tags=["conversations"] | |
| ) | |
| async def list_conversations( | |
| db: AsyncSession = Depends(get_db), | |
| ) -> list[ConversationSummary]: | |
| """List conversations newest first.""" | |
| msg_count = ( | |
| select( | |
| Message.conversation_id, | |
| func.count(Message.id).label("cnt"), | |
| ) | |
| .group_by(Message.conversation_id) | |
| .subquery() | |
| ) | |
| result = await db.execute( | |
| select(Conversation, Lead, msg_count.c.cnt) | |
| .outerjoin(Lead, Lead.conversation_id == Conversation.id) | |
| .outerjoin( | |
| msg_count, msg_count.c.conversation_id == Conversation.id | |
| ) | |
| .order_by(Conversation.created_at.desc()) | |
| .limit(200) | |
| ) | |
| rows = result.all() | |
| items: list[ConversationSummary] = [] | |
| for conv, lead, count in rows: | |
| items.append( | |
| ConversationSummary( | |
| id=conv.id, | |
| channel=conv.channel, | |
| language=conv.language, | |
| status=conv.status, | |
| handed_off=conv.handed_off, | |
| appointment_booked=conv.appointment_booked, | |
| resolved_without_human=conv.resolved_without_human, | |
| message_count=int(count or 0), | |
| created_at=conv.created_at, | |
| lead_name=lead.name if lead else None, | |
| ) | |
| ) | |
| return items | |
| async def get_conversation( | |
| conversation_id: int, | |
| db: AsyncSession = Depends(get_db), | |
| ) -> ConversationDetail: | |
| """Get one conversation with messages.""" | |
| result = await db.execute( | |
| select(Conversation) | |
| .where(Conversation.id == conversation_id) | |
| .options( | |
| selectinload(Conversation.messages), | |
| selectinload(Conversation.lead), | |
| selectinload(Conversation.appointment), | |
| ) | |
| ) | |
| conv = result.scalar_one_or_none() | |
| if conv is None: | |
| raise HTTPException(status_code=404, detail="Not found") | |
| appointment: dict[str, Any] | None = None | |
| if conv.appointment: | |
| appointment = { | |
| "slot_start": conv.appointment.slot_start, | |
| "slot_end": conv.appointment.slot_end, | |
| "status": conv.appointment.status, | |
| "attendee_name": conv.appointment.attendee_name, | |
| "attendee_email": conv.appointment.attendee_email, | |
| } | |
| return ConversationDetail( | |
| id=conv.id, | |
| channel=conv.channel, | |
| language=conv.language, | |
| status=conv.status, | |
| handed_off=conv.handed_off, | |
| appointment_booked=conv.appointment_booked, | |
| resolved_without_human=conv.resolved_without_human, | |
| created_at=conv.created_at, | |
| messages=[ | |
| MessageOut( | |
| id=m.id, | |
| role=m.role, | |
| content=m.content, | |
| language=m.language, | |
| grounded=m.grounded, | |
| created_at=m.created_at, | |
| ) | |
| for m in conv.messages | |
| ], | |
| lead=lead_to_dict(conv.lead), | |
| appointment=appointment, | |
| ) | |
| async def export_conversation( | |
| conversation_id: int, | |
| db: AsyncSession = Depends(get_db), | |
| ) -> StreamingResponse: | |
| """Export conversation messages as CSV.""" | |
| result = await db.execute( | |
| select(Conversation) | |
| .where(Conversation.id == conversation_id) | |
| .options(selectinload(Conversation.messages)) | |
| ) | |
| conv = result.scalar_one_or_none() | |
| if conv is None: | |
| raise HTTPException(status_code=404, detail="Not found") | |
| buffer = io.StringIO() | |
| writer = csv.writer(buffer) | |
| writer.writerow( | |
| ["id", "role", "language", "grounded", "created_at", "content"] | |
| ) | |
| for m in conv.messages: | |
| writer.writerow( | |
| [ | |
| m.id, | |
| m.role, | |
| m.language or "", | |
| m.grounded if m.grounded is not None else "", | |
| m.created_at.isoformat(), | |
| m.content, | |
| ] | |
| ) | |
| buffer.seek(0) | |
| return StreamingResponse( | |
| iter([buffer.getvalue()]), | |
| media_type="text/csv", | |
| headers={ | |
| "Content-Disposition": ( | |
| f'attachment; filename="conversation_{conversation_id}.csv"' | |
| ) | |
| }, | |
| ) | |