from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import Optional, List import uuid from datetime import datetime from ..db import get_db from ..models import CallRecord, CallTranscript, CallAudioSegment from ..storage import get_audio_url from ..schemas import CallRecordCreate, CallRecordUpdate, TranscriptCreate router = APIRouter(prefix="/history", tags=["history"]) def record_to_dict(record: CallRecord) -> dict: return { "id": record.id, "user_id": record.user_id, "assistant_id": record.assistant_id, "source": record.source, "status": record.status, "started_at": record.started_at, "ended_at": record.ended_at, "duration_seconds": record.duration_seconds, "summary": record.summary, "cost": record.cost, "created_at": record.created_at, } @router.get("") def list_history( assistant_id: Optional[str] = None, status: Optional[str] = None, source: Optional[str] = None, page: int = 1, limit: int = 20, db: Session = Depends(get_db) ): """获取通话记录列表""" query = db.query(CallRecord) if assistant_id: query = query.filter(CallRecord.assistant_id == assistant_id) if status: query = query.filter(CallRecord.status == status) if source: query = query.filter(CallRecord.source == source) total = query.count() records = query.order_by(CallRecord.started_at.desc()) \ .offset((page-1)*limit).limit(limit).all() return { "total": total, "page": page, "limit": limit, "list": [record_to_dict(r) for r in records] } @router.get("/{call_id}") def get_history_detail(call_id: str, db: Session = Depends(get_db)): """获取通话详情""" record = db.query(CallRecord).filter(CallRecord.id == call_id).first() if not record: raise HTTPException(status_code=404, detail="Call record not found") # 获取转写 transcripts = db.query(CallTranscript) \ .filter(CallTranscript.call_id == call_id) \ .order_by(CallTranscript.turn_index).all() audio_segments = db.query(CallAudioSegment).filter(CallAudioSegment.call_id == call_id).all() audio_by_turn = {seg.turn_index: seg.audio_url for seg in audio_segments if seg.turn_index is not None} transcript_list = [] for t in transcripts: audio_url = audio_by_turn.get(t.turn_index) or get_audio_url(call_id, t.turn_index) transcript_list.append({ "turnIndex": t.turn_index, "speaker": t.speaker, "content": t.content, "confidence": t.confidence, "startMs": t.start_ms, "endMs": t.end_ms, "durationMs": t.duration_ms, "audioUrl": audio_url, }) return { "id": record.id, "user_id": record.user_id, "assistant_id": record.assistant_id, "source": record.source, "status": record.status, "started_at": record.started_at, "ended_at": record.ended_at, "duration_seconds": record.duration_seconds, "summary": record.summary, "transcripts": transcript_list, } @router.post("") def create_call_record( data: CallRecordCreate, db: Session = Depends(get_db) ): """创建通话记录(引擎回调使用)""" record = CallRecord( id=str(uuid.uuid4())[:8], user_id=data.user_id, assistant_id=data.assistant_id, source=data.source, status=data.status or "connected", started_at=datetime.utcnow().isoformat(), cost=data.cost or 0.0, ) db.add(record) db.commit() db.refresh(record) return record_to_dict(record) @router.put("/{call_id}") def update_call_record( call_id: str, data: CallRecordUpdate, db: Session = Depends(get_db) ): """更新通话记录""" record = db.query(CallRecord).filter(CallRecord.id == call_id).first() if not record: raise HTTPException(status_code=404, detail="Call record not found") if data.status is not None: record.status = data.status if data.summary is not None: record.summary = data.summary if data.duration_seconds is not None: record.duration_seconds = data.duration_seconds record.ended_at = datetime.utcnow().isoformat() if data.ended_at is not None: record.ended_at = data.ended_at if data.cost is not None: record.cost = data.cost if data.metadata is not None: record.call_metadata = data.metadata db.commit() db.refresh(record) return record_to_dict(record) @router.post("/{call_id}/transcripts") def add_transcript( call_id: str, data: TranscriptCreate, db: Session = Depends(get_db) ): """添加转写片段""" record = db.query(CallRecord).filter(CallRecord.id == call_id).first() if not record: raise HTTPException(status_code=404, detail="Call record not found") transcript = CallTranscript( call_id=call_id, turn_index=data.turn_index, speaker=data.speaker, content=data.content, confidence=data.confidence, start_ms=data.start_ms, end_ms=data.end_ms, duration_ms=data.duration_ms, emotion=data.emotion, ) db.add(transcript) db.commit() db.refresh(transcript) # 补充音频 URL audio_url = get_audio_url(call_id, data.turn_index) return { "id": transcript.id, "turn_index": data.turn_index, "speaker": data.speaker, "content": data.content, "confidence": data.confidence, "start_ms": data.start_ms, "end_ms": data.end_ms, "duration_ms": data.duration_ms, "emotion": data.emotion, "audio_url": audio_url, } @router.get("/{call_id}/audio/{turn_index}") def get_audio(call_id: str, turn_index: int): """获取音频文件""" audio_url = get_audio_url(call_id, turn_index) if not audio_url: raise HTTPException(status_code=404, detail="Audio not found") from fastapi.responses import RedirectResponse return RedirectResponse(audio_url) @router.delete("/{call_id}") def delete_call_record(call_id: str, db: Session = Depends(get_db)): """删除通话记录""" record = db.query(CallRecord).filter(CallRecord.id == call_id).first() if not record: raise HTTPException(status_code=404, detail="Call record not found") db.delete(record) db.commit() return {"message": "Deleted successfully"}