from datetime import datetime from typing import List, Optional from pydantic import BaseModel # ============ Voice ============ class VoiceBase(BaseModel): name: str vendor: str gender: str language: str description: str class VoiceOut(VoiceBase): id: str class Config: from_attributes = True # ============ Assistant ============ class AssistantBase(BaseModel): name: str opener: str = "" prompt: str = "" knowledgeBaseId: Optional[str] = None language: str = "zh" voice: Optional[str] = None speed: float = 1.0 hotwords: List[str] = [] tools: List[str] = [] interruptionSensitivity: int = 500 configMode: str = "platform" apiUrl: Optional[str] = None apiKey: Optional[str] = None class AssistantCreate(AssistantBase): pass class AssistantUpdate(AssistantBase): name: Optional[str] = None class AssistantOut(AssistantBase): id: str callCount: int = 0 created_at: Optional[datetime] = None class Config: from_attributes = True # ============ Knowledge Base ============ class KnowledgeDocument(BaseModel): id: str name: str size: str fileType: str = "txt" storageUrl: Optional[str] = None status: str = "pending" chunkCount: int = 0 uploadDate: str class KnowledgeDocumentCreate(BaseModel): name: str size: str fileType: str = "txt" storageUrl: Optional[str] = None class KnowledgeDocumentUpdate(BaseModel): status: Optional[str] = None chunkCount: Optional[int] = None errorMessage: Optional[str] = None class KnowledgeBaseBase(BaseModel): name: str description: str = "" embeddingModel: str = "text-embedding-3-small" chunkSize: int = 500 chunkOverlap: int = 50 class KnowledgeBaseCreate(KnowledgeBaseBase): pass class KnowledgeBaseUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None embeddingModel: Optional[str] = None chunkSize: Optional[int] = None chunkOverlap: Optional[int] = None status: Optional[str] = None class KnowledgeBaseOut(KnowledgeBaseBase): id: str docCount: int = 0 chunkCount: int = 0 status: str = "active" createdAt: Optional[datetime] = None updatedAt: Optional[datetime] = None documents: List[KnowledgeDocument] = [] class Config: from_attributes = True # ============ Knowledge Search ============ class KnowledgeSearchQuery(BaseModel): query: str kb_id: str nResults: int = 5 class KnowledgeSearchResult(BaseModel): query: str results: List[dict] class DocumentIndexRequest(BaseModel): document_id: str content: str class KnowledgeStats(BaseModel): kb_id: str docCount: int chunkCount: int # ============ Workflow ============ class WorkflowNode(BaseModel): name: str type: str isStart: Optional[bool] = None metadata: dict prompt: Optional[str] = None messagePlan: Optional[dict] = None variableExtractionPlan: Optional[dict] = None tool: Optional[dict] = None globalNodePlan: Optional[dict] = None class WorkflowEdge(BaseModel): from_: str to: str label: Optional[str] = None class Config: populate_by_name = True class WorkflowBase(BaseModel): name: str nodeCount: int = 0 createdAt: str = "" updatedAt: str = "" globalPrompt: Optional[str] = None nodes: List[dict] = [] edges: List[dict] = [] class WorkflowCreate(WorkflowBase): pass class WorkflowUpdate(BaseModel): name: Optional[str] = None nodeCount: Optional[int] = None nodes: Optional[List[dict]] = None edges: Optional[List[dict]] = None globalPrompt: Optional[str] = None class WorkflowOut(WorkflowBase): id: str class Config: from_attributes = True # ============ Call Record ============ class TranscriptSegment(BaseModel): turnIndex: int speaker: str # human/ai content: str confidence: Optional[float] = None startMs: int endMs: int durationMs: Optional[int] = None audioUrl: Optional[str] = None class CallRecordCreate(BaseModel): user_id: int assistant_id: Optional[str] = None source: str = "debug" class CallRecordUpdate(BaseModel): status: Optional[str] = None summary: Optional[str] = None duration_seconds: Optional[int] = None class CallRecordOut(BaseModel): id: str user_id: int assistant_id: Optional[str] = None source: str status: str started_at: str ended_at: Optional[str] = None duration_seconds: Optional[int] = None summary: Optional[str] = None transcripts: List[TranscriptSegment] = [] class Config: from_attributes = True # ============ Call Transcript ============ class TranscriptCreate(BaseModel): turn_index: int speaker: str content: str confidence: Optional[float] = None start_ms: int end_ms: int duration_ms: Optional[int] = None emotion: Optional[str] = None class TranscriptOut(TranscriptCreate): id: int audio_url: Optional[str] = None class Config: from_attributes = True # ============ Dashboard ============ class DashboardStats(BaseModel): totalCalls: int answerRate: int avgDuration: str humanTransferCount: int trend: List[dict] # ============ API Response ============ class Message(BaseModel): message: str class DocumentIndexRequest(BaseModel): content: str class ListResponse(BaseModel): total: int page: int limit: int list: List