Files
AI-VideoAssistant/api/app/models.py
2026-02-08 23:16:21 +08:00

230 lines
12 KiB
Python

from datetime import datetime
from typing import List, Optional
from sqlalchemy import String, Integer, DateTime, Text, Float, ForeignKey, JSON, Enum
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)
# ============ Voice ============
class Voice(Base):
__tablename__ = "voices"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), index=True, nullable=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)
model: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) # 厂商语音模型标识
voice_key: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) # 厂商voice_key
api_key: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) # 每个声音独立 API key
base_url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) # 每个声音独立 OpenAI-compatible base_url
speed: Mapped[float] = mapped_column(Float, default=1.0)
gain: Mapped[int] = mapped_column(Integer, default=0)
pitch: Mapped[int] = mapped_column(Integer, default=0)
enabled: Mapped[bool] = mapped_column(default=True)
is_system: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User", foreign_keys=[user_id])
# ============ LLM Model ============
class LLMModel(Base):
__tablename__ = "llm_models"
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(128), nullable=False)
vendor: Mapped[str] = mapped_column(String(64), nullable=False)
type: Mapped[str] = mapped_column(String(32), nullable=False) # text/embedding/rerank
base_url: Mapped[str] = mapped_column(String(512), nullable=False)
api_key: Mapped[str] = mapped_column(String(512), nullable=False)
model_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
temperature: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
context_length: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
enabled: Mapped[bool] = mapped_column(default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User")
# ============ ASR Model ============
class ASRModel(Base):
__tablename__ = "asr_models"
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(128), nullable=False)
vendor: Mapped[str] = mapped_column(String(64), nullable=False)
language: Mapped[str] = mapped_column(String(32), nullable=False) # zh/en/Multi-lingual
base_url: Mapped[str] = mapped_column(String(512), nullable=False)
api_key: Mapped[str] = mapped_column(String(512), nullable=False)
model_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
hotwords: Mapped[dict] = mapped_column(JSON, default=list)
enable_punctuation: Mapped[bool] = mapped_column(default=True)
enable_normalization: Mapped[bool] = mapped_column(default=True)
enabled: Mapped[bool] = mapped_column(default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User")
# ============ Assistant ============
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)
# 模型关联
llm_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
asr_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
embedding_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
rerank_model_id: Mapped[Optional[str]] = mapped_column(String(64), 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")
# ============ Knowledge Base ============
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")
# ============ Workflow ============
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")
# ============ Call Record ============
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")