""" Avatar 形象克隆模型 ================== 存储用户克隆形象的信息,作为本地 localStorage 的云端备份。 """ from datetime import UTC, datetime from sqlalchemy import BigInteger, DateTime, ForeignKey, String, Text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column from app.db.session import Base from app.schemas.enums import AvatarCloneStatus class Avatar(Base): """ 形象克隆记录表 用于备份用户在本地创建的克隆形象,支持换机恢复和客服排查。 """ __tablename__ = "avatars" # 主键:本地生成的唯一标识(与 Kling element_id 无关) id: Mapped[str] = mapped_column( String(64), primary_key=True, comment="本地形象唯一标识(如 avt_xxx)", ) # 关联用户(外键,对应 users.id) user_id: Mapped[str] = mapped_column( UUID(as_uuid=False), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, comment="关联用户 ID", ) # 形象展示名称 name: Mapped[str] = mapped_column( String(64), nullable=False, comment="形象展示名称", ) # 供应商标识 provider: Mapped[str] = mapped_column( String(32), nullable=False, default="kling", comment="供应商标识: kling", ) # Kling 自定义音色 ID(创建成功后回填) voice_id: Mapped[str | None] = mapped_column( String(64), nullable=True, comment="Kling 自定义音色 ID", ) # 供应商主体 ID(创建成功后回填,用于调用 omni-video API) provider_element_id: Mapped[int | None] = mapped_column( BigInteger, nullable=True, comment="供应商主体 ID(数字类型,调用 API 时使用)", ) # 供应商任务 ID(用于客服追溯) provider_voice_job_id: Mapped[str | None] = mapped_column( String(128), nullable=True, index=True, comment="供应商自定义音色任务 ID", ) provider_element_job_id: Mapped[str | None] = mapped_column( String(128), nullable=True, index=True, comment="供应商主体创建任务 ID", ) # 资源地址 video_url: Mapped[str] = mapped_column( Text, nullable=False, comment="原始人物视频 URL", ) trial_url: Mapped[str | None] = mapped_column( Text, nullable=True, comment="音色试听音频 URL", ) # 状态机 status: Mapped[str] = mapped_column( String(32), nullable=False, default=AvatarCloneStatus.PENDING.value, comment="状态: pending/voice_processing/voice_failed/element_processing/element_failed/succeed/timeout", ) # 失败原因(用户可读) fail_reason: Mapped[str | None] = mapped_column( Text, nullable=True, comment="失败原因(中文可读)", ) # 软删除标记 deleted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, comment="软删除时间,NULL 表示未删除", ) # 时间戳 created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(UTC), nullable=False, comment="记录创建时间", ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC), nullable=False, comment="记录更新时间", ) def to_dict(self) -> dict: """转换为字典(用于序列化)""" return {column.name: getattr(self, column.name) for column in self.__table__.columns}