Update backend schema
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, DeclarativeBase
|
||||
import os
|
||||
|
||||
DATABASE_URL = "sqlite:///./data/app.db"
|
||||
# 使用绝对路径
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
DATABASE_URL = f"sqlite:///{os.path.join(BASE_DIR, 'data', 'app.db')}"
|
||||
|
||||
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
@@ -42,31 +42,4 @@ def root():
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# 初始化默认数据
|
||||
@app.on_event("startup")
|
||||
def init_default_data():
|
||||
from sqlalchemy.orm import Session
|
||||
from .db import SessionLocal
|
||||
from .models import Voice
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 检查是否已有数据
|
||||
if db.query(Voice).count() == 0:
|
||||
# 插入默认声音
|
||||
voices = [
|
||||
Voice(id="v1", name="Xiaoyun", vendor="Ali", gender="Female", language="zh", description="Gentle and professional."),
|
||||
Voice(id="v2", name="Kevin", vendor="Volcano", gender="Male", language="en", description="Deep and authoritative."),
|
||||
Voice(id="v3", name="Abby", vendor="Minimax", gender="Female", language="en", description="Cheerful and lively."),
|
||||
Voice(id="v4", name="Guang", vendor="Ali", gender="Male", language="zh", description="Standard newscast style."),
|
||||
Voice(id="v5", name="Doubao", vendor="Volcano", gender="Female", language="zh", description="Cute and young."),
|
||||
]
|
||||
for v in voices:
|
||||
db.add(v)
|
||||
db.commit()
|
||||
print("✅ 默认声音数据已初始化")
|
||||
finally:
|
||||
db.close()
|
||||
return {"status": "ok"}
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import String, Integer, DateTime, Text, Float, ForeignKey, JSON
|
||||
from sqlalchemy import String, Integer, DateTime, Text, Float, ForeignKey, JSON, Enum
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from .db import Base
|
||||
@@ -15,18 +15,72 @@ class User(Base):
|
||||
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)
|
||||
voice_params: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
model: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) # 厂商语音模型标识
|
||||
voice_key: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) # 厂商voice_key
|
||||
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"
|
||||
|
||||
@@ -46,6 +100,11 @@ class Assistant(Base):
|
||||
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)
|
||||
|
||||
@@ -53,6 +112,7 @@ class Assistant(Base):
|
||||
call_records = relationship("CallRecord", back_populates="assistant")
|
||||
|
||||
|
||||
# ============ Knowledge Base ============
|
||||
class KnowledgeBase(Base):
|
||||
__tablename__ = "knowledge_bases"
|
||||
|
||||
@@ -92,6 +152,7 @@ class KnowledgeDocument(Base):
|
||||
kb = relationship("KnowledgeBase", back_populates="documents")
|
||||
|
||||
|
||||
# ============ Workflow ============
|
||||
class Workflow(Base):
|
||||
__tablename__ = "workflows"
|
||||
|
||||
@@ -108,6 +169,7 @@ class Workflow(Base):
|
||||
user = relationship("User")
|
||||
|
||||
|
||||
# ============ Call Record ============
|
||||
class CallRecord(Base):
|
||||
__tablename__ = "call_records"
|
||||
|
||||
|
||||
@@ -1,24 +1,203 @@
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# ============ Enums ============
|
||||
class AssistantConfigMode(str, Enum):
|
||||
PLATFORM = "platform"
|
||||
DIFY = "dify"
|
||||
FASTGPT = "fastgpt"
|
||||
NONE = "none"
|
||||
|
||||
|
||||
class LLMModelType(str, Enum):
|
||||
TEXT = "text"
|
||||
EMBEDDING = "embedding"
|
||||
RERANK = "rerank"
|
||||
|
||||
|
||||
class ASRLanguage(str, Enum):
|
||||
ZH = "zh"
|
||||
EN = "en"
|
||||
MULTILINGUAL = "Multi-lingual"
|
||||
|
||||
|
||||
class VoiceGender(str, Enum):
|
||||
MALE = "Male"
|
||||
FEMALE = "Female"
|
||||
|
||||
|
||||
class CallRecordSource(str, Enum):
|
||||
DEBUG = "debug"
|
||||
EXTERNAL = "external"
|
||||
|
||||
|
||||
class CallRecordStatus(str, Enum):
|
||||
CONNECTED = "connected"
|
||||
MISSED = "missed"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
# ============ Voice ============
|
||||
class VoiceBase(BaseModel):
|
||||
name: str
|
||||
vendor: str
|
||||
gender: str
|
||||
language: str
|
||||
description: str
|
||||
gender: str # "Male" | "Female"
|
||||
language: str # "zh" | "en"
|
||||
description: str = ""
|
||||
|
||||
|
||||
class VoiceCreate(VoiceBase):
|
||||
model: str # 厂商语音模型标识
|
||||
voice_key: str # 厂商voice_key
|
||||
speed: float = 1.0
|
||||
gain: int = 0
|
||||
pitch: int = 0
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class VoiceUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
voice_key: Optional[str] = None
|
||||
speed: Optional[float] = None
|
||||
gain: Optional[int] = None
|
||||
pitch: Optional[int] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class VoiceOut(VoiceBase):
|
||||
id: str
|
||||
user_id: Optional[int] = None
|
||||
model: Optional[str] = None
|
||||
voice_key: Optional[str] = None
|
||||
speed: float = 1.0
|
||||
gain: int = 0
|
||||
pitch: int = 0
|
||||
enabled: bool = True
|
||||
is_system: bool = False
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class VoicePreviewRequest(BaseModel):
|
||||
text: str
|
||||
speed: Optional[float] = None
|
||||
gain: Optional[int] = None
|
||||
pitch: Optional[int] = None
|
||||
|
||||
|
||||
class VoicePreviewResponse(BaseModel):
|
||||
success: bool
|
||||
audio_url: Optional[str] = None
|
||||
duration_ms: Optional[int] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
# ============ LLM Model ============
|
||||
class LLMModelBase(BaseModel):
|
||||
name: str
|
||||
vendor: str
|
||||
type: LLMModelType
|
||||
base_url: str
|
||||
api_key: str
|
||||
model_name: Optional[str] = None
|
||||
temperature: Optional[float] = None
|
||||
context_length: Optional[int] = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class LLMModelCreate(LLMModelBase):
|
||||
pass
|
||||
|
||||
|
||||
class LLMModelUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
model_name: Optional[str] = None
|
||||
temperature: Optional[float] = None
|
||||
context_length: Optional[int] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class LLMModelOut(LLMModelBase):
|
||||
id: str
|
||||
user_id: int
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class LLMModelTestResponse(BaseModel):
|
||||
success: bool
|
||||
latency_ms: Optional[int] = None
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
# ============ ASR Model ============
|
||||
class ASRModelBase(BaseModel):
|
||||
name: str
|
||||
vendor: str
|
||||
language: str # "zh" | "en" | "Multi-lingual"
|
||||
base_url: str
|
||||
api_key: str
|
||||
model_name: Optional[str] = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class ASRModelCreate(ASRModelBase):
|
||||
hotwords: List[str] = []
|
||||
enable_punctuation: bool = True
|
||||
enable_normalization: bool = True
|
||||
|
||||
|
||||
class ASRModelUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
model_name: Optional[str] = None
|
||||
hotwords: Optional[List[str]] = None
|
||||
enable_punctuation: Optional[bool] = None
|
||||
enable_normalization: Optional[bool] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class ASRModelOut(ASRModelBase):
|
||||
id: str
|
||||
user_id: int
|
||||
hotwords: List[str] = []
|
||||
enable_punctuation: bool = True
|
||||
enable_normalization: bool = True
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ASRTestRequest(BaseModel):
|
||||
audio_url: Optional[str] = None
|
||||
audio_data: Optional[str] = None # base64 encoded
|
||||
|
||||
|
||||
class ASRTestResponse(BaseModel):
|
||||
success: bool
|
||||
transcript: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
confidence: Optional[float] = None
|
||||
duration_ms: Optional[int] = None
|
||||
latency_ms: Optional[int] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
# ============ Assistant ============
|
||||
class AssistantBase(BaseModel):
|
||||
name: str
|
||||
@@ -34,25 +213,56 @@ class AssistantBase(BaseModel):
|
||||
configMode: str = "platform"
|
||||
apiUrl: Optional[str] = None
|
||||
apiKey: Optional[str] = None
|
||||
# 模型关联
|
||||
llmModelId: Optional[str] = None
|
||||
asrModelId: Optional[str] = None
|
||||
embeddingModelId: Optional[str] = None
|
||||
rerankModelId: Optional[str] = None
|
||||
|
||||
|
||||
class AssistantCreate(AssistantBase):
|
||||
pass
|
||||
|
||||
|
||||
class AssistantUpdate(AssistantBase):
|
||||
class AssistantUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
opener: Optional[str] = None
|
||||
prompt: Optional[str] = None
|
||||
knowledgeBaseId: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
voice: Optional[str] = None
|
||||
speed: Optional[float] = None
|
||||
hotwords: Optional[List[str]] = None
|
||||
tools: Optional[List[str]] = None
|
||||
interruptionSensitivity: Optional[int] = None
|
||||
configMode: Optional[str] = None
|
||||
apiUrl: Optional[str] = None
|
||||
apiKey: Optional[str] = None
|
||||
llmModelId: Optional[str] = None
|
||||
asrModelId: Optional[str] = None
|
||||
embeddingModelId: Optional[str] = None
|
||||
rerankModelId: Optional[str] = None
|
||||
|
||||
|
||||
class AssistantOut(AssistantBase):
|
||||
id: str
|
||||
callCount: int = 0
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class AssistantStats(BaseModel):
|
||||
assistant_id: str
|
||||
total_calls: int = 0
|
||||
connected_calls: int = 0
|
||||
missed_calls: int = 0
|
||||
avg_duration_seconds: float = 0.0
|
||||
today_calls: int = 0
|
||||
|
||||
|
||||
# ============ Knowledge Base ============
|
||||
class KnowledgeDocument(BaseModel):
|
||||
id: str
|
||||
@@ -196,6 +406,7 @@ class TranscriptSegment(BaseModel):
|
||||
endMs: int
|
||||
durationMs: Optional[int] = None
|
||||
audioUrl: Optional[str] = None
|
||||
emotion: Optional[str] = None
|
||||
|
||||
|
||||
class CallRecordCreate(BaseModel):
|
||||
@@ -208,6 +419,9 @@ class CallRecordUpdate(BaseModel):
|
||||
status: Optional[str] = None
|
||||
summary: Optional[str] = None
|
||||
duration_seconds: Optional[int] = None
|
||||
ended_at: Optional[str] = None
|
||||
cost: Optional[float] = None
|
||||
metadata: Optional[dict] = None
|
||||
|
||||
|
||||
class CallRecordOut(BaseModel):
|
||||
@@ -220,6 +434,9 @@ class CallRecordOut(BaseModel):
|
||||
ended_at: Optional[str] = None
|
||||
duration_seconds: Optional[int] = None
|
||||
summary: Optional[str] = None
|
||||
cost: float = 0.0
|
||||
metadata: dict = {}
|
||||
created_at: Optional[datetime] = None
|
||||
transcripts: List[TranscriptSegment] = []
|
||||
|
||||
class Config:
|
||||
@@ -246,6 +463,19 @@ class TranscriptOut(TranscriptCreate):
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============ History Stats ============
|
||||
class HistoryStats(BaseModel):
|
||||
total_calls: int = 0
|
||||
connected_calls: int = 0
|
||||
missed_calls: int = 0
|
||||
failed_calls: int = 0
|
||||
avg_duration_seconds: float = 0.0
|
||||
total_cost: float = 0.0
|
||||
by_status: dict = {}
|
||||
by_source: dict = {}
|
||||
daily_trend: List[dict] = []
|
||||
|
||||
|
||||
# ============ Dashboard ============
|
||||
class DashboardStats(BaseModel):
|
||||
totalCalls: int
|
||||
@@ -269,3 +499,9 @@ class ListResponse(BaseModel):
|
||||
page: int
|
||||
limit: int
|
||||
list: List
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
id: str
|
||||
started_at: str
|
||||
matched_content: Optional[str] = None
|
||||
|
||||
Reference in New Issue
Block a user