226 lines
10 KiB
Python
226 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import JSON, BigInteger, DateTime, Float, ForeignKey, Integer, String, Text, func
|
|
from sqlalchemy.dialects.mysql import LONGTEXT
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from ..db.base import Base
|
|
|
|
# 自定义列类型:兼容跨数据库环境
|
|
BIGINT_PK_TYPE = BigInteger().with_variant(Integer, "sqlite")
|
|
LONG_TEXT_TYPE = Text().with_variant(LONGTEXT, "mysql")
|
|
|
|
|
|
class _MetadataAccessor:
|
|
"""Descriptor 用于将 `metadata` 访问重定向到 `metadata_`,且保持 Base.metadata 可用。"""
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return Base.metadata
|
|
return instance.metadata_
|
|
|
|
def __set__(self, instance, value):
|
|
instance.metadata_ = value
|
|
|
|
|
|
class NovelProject(Base):
|
|
"""小说项目主表,仅存放轻量级元数据。"""
|
|
|
|
__tablename__ = "novel_projects"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
initial_prompt: Mapped[Optional[str]] = mapped_column(Text)
|
|
status: Mapped[str] = mapped_column(String(32), default="draft")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
owner: Mapped["User"] = relationship("User", back_populates="novel_projects")
|
|
blueprint: Mapped[Optional["NovelBlueprint"]] = relationship(
|
|
back_populates="project", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
conversations: Mapped[list["NovelConversation"]] = relationship(
|
|
back_populates="project", cascade="all, delete-orphan", order_by="NovelConversation.seq"
|
|
)
|
|
characters: Mapped[list["BlueprintCharacter"]] = relationship(
|
|
back_populates="project", cascade="all, delete-orphan", order_by="BlueprintCharacter.position"
|
|
)
|
|
relationships_: Mapped[list["BlueprintRelationship"]] = relationship(
|
|
back_populates="project", cascade="all, delete-orphan", order_by="BlueprintRelationship.position"
|
|
)
|
|
outlines: Mapped[list["ChapterOutline"]] = relationship(
|
|
back_populates="project", cascade="all, delete-orphan", order_by="ChapterOutline.chapter_number"
|
|
)
|
|
chapters: Mapped[list["Chapter"]] = relationship(
|
|
back_populates="project", cascade="all, delete-orphan", order_by="Chapter.chapter_number"
|
|
)
|
|
|
|
|
|
class NovelConversation(Base):
|
|
"""对话记录表,存储概念阶段的连续对话。"""
|
|
|
|
__tablename__ = "novel_conversations"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("novel_projects.id", ondelete="CASCADE"), nullable=False)
|
|
seq: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
role: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
content: Mapped[str] = mapped_column(LONG_TEXT_TYPE, nullable=False)
|
|
metadata_: Mapped[Optional[dict]] = mapped_column("metadata", JSON)
|
|
metadata = _MetadataAccessor()
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
project: Mapped[NovelProject] = relationship(back_populates="conversations")
|
|
|
|
|
|
class NovelBlueprint(Base):
|
|
"""蓝图主体信息(标题、风格等)。"""
|
|
|
|
__tablename__ = "novel_blueprints"
|
|
|
|
project_id: Mapped[str] = mapped_column(
|
|
ForeignKey("novel_projects.id", ondelete="CASCADE"), primary_key=True
|
|
)
|
|
title: Mapped[Optional[str]] = mapped_column(String(255))
|
|
target_audience: Mapped[Optional[str]] = mapped_column(String(255))
|
|
genre: Mapped[Optional[str]] = mapped_column(String(128))
|
|
style: Mapped[Optional[str]] = mapped_column(String(128))
|
|
tone: Mapped[Optional[str]] = mapped_column(String(128))
|
|
one_sentence_summary: Mapped[Optional[str]] = mapped_column(Text)
|
|
full_synopsis: Mapped[Optional[str]] = mapped_column(LONG_TEXT_TYPE)
|
|
world_setting: Mapped[Optional[dict]] = mapped_column(JSON, default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
project: Mapped[NovelProject] = relationship(back_populates="blueprint")
|
|
|
|
|
|
class BlueprintCharacter(Base):
|
|
"""蓝图角色信息。"""
|
|
|
|
__tablename__ = "blueprint_characters"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("novel_projects.id", ondelete="CASCADE"), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
identity: Mapped[Optional[str]] = mapped_column(String(255))
|
|
personality: Mapped[Optional[str]] = mapped_column(Text)
|
|
goals: Mapped[Optional[str]] = mapped_column(Text)
|
|
abilities: Mapped[Optional[str]] = mapped_column(Text)
|
|
relationship_to_protagonist: Mapped[Optional[str]] = mapped_column(Text)
|
|
extra: Mapped[Optional[dict]] = mapped_column(JSON)
|
|
position: Mapped[int] = mapped_column(Integer, default=0)
|
|
|
|
project: Mapped[NovelProject] = relationship(back_populates="characters")
|
|
|
|
|
|
class BlueprintRelationship(Base):
|
|
"""角色之间的关系。"""
|
|
|
|
__tablename__ = "blueprint_relationships"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("novel_projects.id", ondelete="CASCADE"), nullable=False)
|
|
character_from: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
character_to: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[Optional[str]] = mapped_column(Text)
|
|
position: Mapped[int] = mapped_column(Integer, default=0)
|
|
|
|
project: Mapped[NovelProject] = relationship(back_populates="relationships_")
|
|
|
|
|
|
class ChapterOutline(Base):
|
|
"""章节纲要。"""
|
|
|
|
__tablename__ = "chapter_outlines"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("novel_projects.id", ondelete="CASCADE"), nullable=False)
|
|
chapter_number: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
summary: Mapped[Optional[str]] = mapped_column(Text)
|
|
|
|
project: Mapped[NovelProject] = relationship(back_populates="outlines")
|
|
|
|
|
|
class Chapter(Base):
|
|
"""章节正文状态,指向选中的版本。"""
|
|
|
|
__tablename__ = "chapters"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("novel_projects.id", ondelete="CASCADE"), nullable=False)
|
|
chapter_number: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
real_summary: Mapped[Optional[str]] = mapped_column(Text)
|
|
status: Mapped[str] = mapped_column(String(32), default="not_generated")
|
|
word_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
selected_version_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("chapter_versions.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
project: Mapped[NovelProject] = relationship(back_populates="chapters")
|
|
versions: Mapped[list["ChapterVersion"]] = relationship(
|
|
"ChapterVersion",
|
|
back_populates="chapter",
|
|
cascade="all, delete-orphan",
|
|
order_by="ChapterVersion.created_at",
|
|
primaryjoin="Chapter.id == ChapterVersion.chapter_id",
|
|
foreign_keys="[ChapterVersion.chapter_id]",
|
|
)
|
|
selected_version: Mapped[Optional["ChapterVersion"]] = relationship(
|
|
"ChapterVersion",
|
|
foreign_keys=[selected_version_id],
|
|
primaryjoin="Chapter.selected_version_id == ChapterVersion.id",
|
|
post_update=True,
|
|
)
|
|
evaluations: Mapped[list["ChapterEvaluation"]] = relationship(
|
|
back_populates="chapter", cascade="all, delete-orphan", order_by="ChapterEvaluation.created_at"
|
|
)
|
|
|
|
|
|
class ChapterVersion(Base):
|
|
"""章节生成的不同版本文本。"""
|
|
|
|
__tablename__ = "chapter_versions"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
chapter_id: Mapped[int] = mapped_column(ForeignKey("chapters.id", ondelete="CASCADE"), nullable=False)
|
|
version_label: Mapped[Optional[str]] = mapped_column(String(64))
|
|
provider: Mapped[Optional[str]] = mapped_column(String(64))
|
|
content: Mapped[str] = mapped_column(LONG_TEXT_TYPE, nullable=False)
|
|
metadata_: Mapped[Optional[dict]] = mapped_column("metadata", JSON)
|
|
metadata = _MetadataAccessor()
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
chapter: Mapped[Chapter] = relationship(
|
|
"Chapter",
|
|
back_populates="versions",
|
|
foreign_keys=[chapter_id],
|
|
)
|
|
evaluations: Mapped[list["ChapterEvaluation"]] = relationship(
|
|
back_populates="version", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class ChapterEvaluation(Base):
|
|
"""章节评估记录。"""
|
|
|
|
__tablename__ = "chapter_evaluations"
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT_PK_TYPE, primary_key=True, autoincrement=True)
|
|
chapter_id: Mapped[int] = mapped_column(ForeignKey("chapters.id", ondelete="CASCADE"), nullable=False)
|
|
version_id: Mapped[Optional[int]] = mapped_column(ForeignKey("chapter_versions.id", ondelete="CASCADE"))
|
|
decision: Mapped[Optional[str]] = mapped_column(String(32))
|
|
feedback: Mapped[Optional[str]] = mapped_column(Text)
|
|
score: Mapped[Optional[float]] = mapped_column(Float)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
chapter: Mapped[Chapter] = relationship(back_populates="evaluations")
|
|
version: Mapped[Optional[ChapterVersion]] = relationship(back_populates="evaluations")
|