Files
AI-VideoAssistant/api/app/models.py
2026-02-06 14:01:34 +08:00

166 lines
8.4 KiB
Python

from datetime import datetime
from typing import List, Optional
from sqlalchemy import String, Integer, DateTime, Text, Float, ForeignKey, JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .db import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
class Voice(Base):
__tablename__ = "voices"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
vendor: Mapped[str] = mapped_column(String(64), nullable=False)
gender: Mapped[str] = mapped_column(String(32), nullable=False)
language: Mapped[str] = mapped_column(String(16), nullable=False)
description: Mapped[str] = mapped_column(String(255), nullable=False)
voice_params: Mapped[dict] = mapped_column(JSON, default=dict)
class Assistant(Base):
__tablename__ = "assistants"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
call_count: Mapped[int] = mapped_column(Integer, default=0)
opener: Mapped[str] = mapped_column(Text, default="")
prompt: Mapped[str] = mapped_column(Text, default="")
knowledge_base_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
language: Mapped[str] = mapped_column(String(16), default="zh")
voice: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
speed: Mapped[float] = mapped_column(Float, default=1.0)
hotwords: Mapped[dict] = mapped_column(JSON, default=list)
tools: Mapped[dict] = mapped_column(JSON, default=list)
interruption_sensitivity: Mapped[int] = mapped_column(Integer, default=500)
config_mode: Mapped[str] = mapped_column(String(32), default="platform")
api_url: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
api_key: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User")
call_records = relationship("CallRecord", back_populates="assistant")
class KnowledgeBase(Base):
__tablename__ = "knowledge_bases"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str] = mapped_column(Text, default="")
embedding_model: Mapped[str] = mapped_column(String(64), default="text-embedding-3-small")
chunk_size: Mapped[int] = mapped_column(Integer, default=500)
chunk_overlap: Mapped[int] = mapped_column(Integer, default=50)
doc_count: Mapped[int] = mapped_column(Integer, default=0)
chunk_count: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(32), default="active")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User")
documents = relationship("KnowledgeDocument", back_populates="kb")
class KnowledgeDocument(Base):
__tablename__ = "knowledge_documents"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
kb_id: Mapped[str] = mapped_column(String(64), ForeignKey("knowledge_bases.id"), index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
size: Mapped[str] = mapped_column(String(64), nullable=False)
file_type: Mapped[str] = mapped_column(String(32), default="txt")
storage_url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
status: Mapped[str] = mapped_column(String(32), default="pending") # pending/processing/completed/failed
chunk_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
upload_date: Mapped[str] = mapped_column(String(32), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
processed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
kb = relationship("KnowledgeBase", back_populates="documents")
class Workflow(Base):
__tablename__ = "workflows"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
node_count: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[str] = mapped_column(String(32), default="")
updated_at: Mapped[str] = mapped_column(String(32), default="")
global_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
nodes: Mapped[dict] = mapped_column(JSON, default=list)
edges: Mapped[dict] = mapped_column(JSON, default=list)
user = relationship("User")
class CallRecord(Base):
__tablename__ = "call_records"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
assistant_id: Mapped[Optional[str]] = mapped_column(String(64), ForeignKey("assistants.id"), index=True)
source: Mapped[str] = mapped_column(String(32), default="debug")
status: Mapped[str] = mapped_column(String(32), default="connected")
started_at: Mapped[str] = mapped_column(String(32), nullable=False)
ended_at: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
duration_seconds: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
cost: Mapped[float] = mapped_column(Float, default=0.0)
call_metadata: Mapped[dict] = mapped_column(JSON, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User")
assistant = relationship("Assistant", back_populates="call_records")
transcripts = relationship("CallTranscript", back_populates="call_record")
audio_segments = relationship("CallAudioSegment", back_populates="call_record")
class CallTranscript(Base):
__tablename__ = "call_transcripts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
call_id: Mapped[str] = mapped_column(String(64), ForeignKey("call_records.id"), index=True)
turn_index: Mapped[int] = mapped_column(Integer, nullable=False)
speaker: Mapped[str] = mapped_column(String(16), nullable=False) # human/ai
content: Mapped[str] = mapped_column(Text, nullable=False)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
start_ms: Mapped[int] = mapped_column(Integer, nullable=False)
end_ms: Mapped[int] = mapped_column(Integer, nullable=False)
duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
emotion: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
call_record = relationship("CallRecord", back_populates="transcripts")
class CallAudioSegment(Base):
__tablename__ = "call_audio_segments"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
call_id: Mapped[str] = mapped_column(String(64), ForeignKey("call_records.id"), index=True)
transcript_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("call_transcripts.id"), nullable=True)
turn_index: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
audio_url: Mapped[str] = mapped_column(String(512), nullable=False)
audio_format: Mapped[str] = mapped_column(String(16), default="mp3")
file_size_bytes: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
start_ms: Mapped[int] = mapped_column(Integer, nullable=False)
end_ms: Mapped[int] = mapped_column(Integer, nullable=False)
duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
call_record = relationship("CallRecord", back_populates="audio_segments")