Files
chenxin b419e0afde M7:形仪与语音——真形锚点、候选定相、声音档案与克隆授权、ASR 接入
- 形仪域 domains/xingyi:form_anchors(稳定辨识特征/年龄表现/许可禁忌/可变范围——生成内容不可静默覆盖锚点);forms(主仪/化仪/文字形仪,candidate→adopt 定相治理:未定相不进呈现位、主仪唯一、新主仪自动退役旧主仪);voice_profiles(三来源,授权克隆必填用途与传播范围,revoke-grant 撤回立即生效,assert-usable 合成前置校验拒绝撤回/过期)
- ASR:元炁 SenseVoice /v1/audio/transcriptions 透传(Whisper 兼容 multipart);YUANQI_BASE_URL 未配置时 503 不降级不伪造——多模态关闭纪律
- 前端形仪工坊(人物详情页):真形锚点编辑(特征逐行解析)/候选登记定相废弃/声音档案建立与授权撤回;头像呈现位改走定相主仪(forms/primary);xingyi api 库 + mock + 组件 4 用例
- 退役:旧 avatar 模块(services/endpoints/avatar、avatar_dir 配置、avatar_url 字段、前端直传组件与测试)
- 验收(test_m7_xingyi 三用例):未定相不成为正式形仪(含主仪唯一与旧主仪 retired 断言);克隆授权可撤回(缺用途拒/撤回即拒/过期同理/预设不受约束);关闭全部多模态后文字相伴完整可用(ASR 503 时镜树缘契百工照常——回归)
- 四套件:后端 376、前端 203、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 008 净库+±1 干净
2026-08-27 20:26:52 +08:00

89 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""形仪域表模型:真形锚点 / 主仪化仪(候选定相)/ 声音档案。
设计依据 core-domains.md §5 与 PRD §8:
- 真形锚点记录稳定辨识特征、年龄表现、许可、禁忌与可变范围;
- 生成内容先为 candidate,用户定相(adopt)后才成为正式主仪或化仪;
- 声音三来源(合成预设/原创设计/授权克隆);克隆授权按身份、用途、期限与
传播范围治理,可撤回,撤回立即生效。
"""
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.ids import new_id
from app.core.time import utc_now
from app.db.base import Base
class FormAnchor(Base):
"""真形锚点:不可被生成内容静默覆盖的稳定辨识约束。"""
__tablename__ = "form_anchors"
id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("anchor"))
character_id: Mapped[str] = mapped_column(
String(80), ForeignKey("characters.id"), nullable=False, index=True
)
# 稳定辨识特征(发色/瞳色/身形/标志性印记等)
features_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
# 年龄表现约束(成人角色恒定成人表现;不得生成未成年敏感形态)
age_presentation: Mapped[str] = mapped_column(String(40), nullable=False, default="adult")
# 许可与禁忌(允许的画风/场景;禁止的表现)
permissions_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
# 可变范围(服装/表情/姿态可变;核心特征不可变)
mutable_scope_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utc_now, onupdate=utc_now
)
class Form(Base):
"""主仪/化仪:一魂多仪。candidate → adopted(定相)/ discarded;同一时刻仅一主仪。"""
__tablename__ = "forms"
id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("form"))
character_id: Mapped[str] = mapped_column(
String(80), ForeignKey("characters.id"), nullable=False, index=True
)
# primary 主仪 / costume 化仪 / text 文字形仪(启镜最小形态)
kind: Mapped[str] = mapped_column(String(20), nullable=False, default="costume", index=True)
# candidate(候选,未定相不转正)/ adopted / discarded / retired
status: Mapped[str] = mapped_column(String(20), nullable=False, default="candidate", index=True)
# 来源:image_gen(生成)/ upload(上传)/ live2d(模型导入)/ text
source: Mapped[str] = mapped_column(String(20), nullable=False, default="upload")
# 文字描述(所有形态必填——文字形仪或图像的文字说明)
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
# 媒体引用(图片文件路径或 Live2D 模型包引用;文字形仪为空)
media_ref: Mapped[str | None] = mapped_column(String(500))
# Live2D 动作映射(模型 motions/expressions 索引,JSON)
live2d_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
adopted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
class VoiceProfile(Base):
"""声音档案:合成预设 / 原创设计 / 授权克隆。"""
__tablename__ = "voice_profiles"
id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("voice"))
character_id: Mapped[str] = mapped_column(
String(80), ForeignKey("characters.id"), nullable=False, index=True
)
# synthesis_preset / original_design / authorized_clone
source: Mapped[str] = mapped_column(String(30), nullable=False, default="synthesis_preset")
name: Mapped[str] = mapped_column(String(120), nullable=False)
# 合成参数(音色/语速/音高;预设名或设计参数)
params_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
# 克隆授权(authorized_clone 必填):身份、用途、期限、传播范围;可撤回
grant_purpose: Mapped[str | None] = mapped_column(String(200))
grant_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
grant_distribution: Mapped[str | None] = mapped_column(String(200))
grant_revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
is_active: Mapped[bool] = mapped_column(default=True, nullable=False, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)