"""世谱(Lorebook / World Info)模型。 兼容酒馆 character_book 结构 + 魂镜扩展(scope / bound_entity_id)。 纯逻辑(匹配 / 预算 / 分组)在 app.modules.lorebook,本文件仅持久化载体。 """ from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column from app.core.ids import new_lorebook_entry_id, new_lorebook_id from app.core.time import utc_now from app.db.base import Base class Lorebook(Base): """一本世谱:条目集合 + 扫描 / 预算参数。 ``character_id`` 为空表示 global 作用域(跨人物共享);非空时按人物隔离。 """ __tablename__ = "lorebooks" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=new_lorebook_id) character_id: Mapped[str | None] = mapped_column( String(80), ForeignKey("characters.id"), index=True ) name: Mapped[str] = mapped_column(String(200), nullable=False, default="") description: Mapped[str] = mapped_column(Text, nullable=False, default="") scan_depth: Mapped[int | None] = mapped_column(Integer) token_budget: Mapped[int | None] = mapped_column(Integer) recursive_scanning: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) scope: Mapped[str] = mapped_column(String(40), nullable=False, default="character", index=True) status: Mapped[str] = mapped_column(String(40), nullable=False, default="active", index=True) owner_scope: Mapped[str] = mapped_column(String(80), nullable=False, index=True) 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 LorebookEntry(Base): """单条世谱条目:keys 命中后注入 content。 ``keys`` / ``secondary_keys`` 为字符串数组,落库存 JSON 文本(契约字典 §4)。 """ __tablename__ = "lorebook_entries" id: Mapped[str] = mapped_column( String(80), primary_key=True, default=new_lorebook_entry_id ) lorebook_id: Mapped[str] = mapped_column( String(80), ForeignKey("lorebooks.id"), nullable=False, index=True ) keys_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]") content: Mapped[str] = mapped_column(Text, nullable=False, default="") secondary_keys_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]") constant: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) selective: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) case_sensitive: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) use_regex: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) position: Mapped[str] = mapped_column(String(40), nullable=False, default="after_char") insertion_order: Mapped[int] = mapped_column(Integer, nullable=False, default=100) priority: Mapped[int] = mapped_column(Integer, nullable=False, default=10) comment: Mapped[str] = mapped_column(String(500), nullable=False, default="") # 软引用河图世界实体(绑定触发);不加 FK,避免跨枢强耦合 bound_entity_id: Mapped[str | None] = mapped_column(String(80), index=True) 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 )