2025-11-04 14:38:59 +08:00
|
|
|
"""长期记忆数据模型 - 支持向量检索和剧情分析"""
|
|
|
|
|
from sqlalchemy import Column, String, Text, Integer, DateTime, ForeignKey, Float, JSON, Boolean
|
|
|
|
|
from sqlalchemy.sql import func
|
|
|
|
|
from app.database import Base
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StoryMemory(Base):
|
|
|
|
|
"""故事记忆表 - 存储结构化的故事片段和元数据"""
|
|
|
|
|
__tablename__ = "story_memories"
|
|
|
|
|
|
2025-11-10 21:16:55 +08:00
|
|
|
id = Column(String(100), primary_key=True, default=lambda: str(uuid.uuid4()))
|
2025-11-04 14:38:59 +08:00
|
|
|
project_id = Column(String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
chapter_id = Column(String(36), ForeignKey("chapters.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
|
|
|
|
|
|
|
|
# 记忆类型
|
|
|
|
|
memory_type = Column(String(50), nullable=False, index=True, comment="""
|
|
|
|
|
记忆类型:
|
|
|
|
|
- plot_point: 情节点
|
|
|
|
|
- character_event: 角色事件
|
|
|
|
|
- world_detail: 世界观细节
|
|
|
|
|
- hook: 钩子(悬念/冲突)
|
|
|
|
|
- foreshadow: 伏笔
|
|
|
|
|
- dialogue: 重要对话
|
|
|
|
|
- scene: 场景描写
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 记忆内容
|
|
|
|
|
title = Column(String(200), comment="记忆标题/简述")
|
|
|
|
|
content = Column(Text, nullable=False, comment="记忆内容摘要(100-500字)")
|
|
|
|
|
full_context = Column(Text, comment="完整上下文(可选,用于详细记录)")
|
|
|
|
|
|
|
|
|
|
# 关联信息
|
|
|
|
|
related_characters = Column(JSON, comment="涉及角色ID列表: ['char_id_1', 'char_id_2']")
|
|
|
|
|
related_locations = Column(JSON, comment="涉及地点列表: ['地点1', '地点2']")
|
|
|
|
|
tags = Column(JSON, comment="标签列表: ['悬念', '转折', '伏笔', '高潮']")
|
|
|
|
|
|
|
|
|
|
# 重要性评分 (用于过滤和排序)
|
|
|
|
|
importance_score = Column(Float, default=0.5, comment="重要性评分 0.0-1.0")
|
|
|
|
|
|
|
|
|
|
# 时间线定位
|
|
|
|
|
story_timeline = Column(Integer, nullable=False, index=True, comment="故事时间线位置(章节序号)")
|
|
|
|
|
chapter_position = Column(Integer, default=0, comment="章节内位置(字符位置)")
|
|
|
|
|
text_length = Column(Integer, default=0, comment="文本长度(字符数)")
|
|
|
|
|
|
|
|
|
|
# 伏笔相关字段
|
|
|
|
|
is_foreshadow = Column(Integer, default=0, comment="伏笔状态: 0=普通记忆, 1=已埋下伏笔, 2=伏笔已回收")
|
2025-11-10 21:16:55 +08:00
|
|
|
foreshadow_resolved_at = Column(String(100), ForeignKey("chapters.id", ondelete="SET NULL"), comment="伏笔回收的章节ID")
|
2025-11-04 14:38:59 +08:00
|
|
|
foreshadow_strength = Column(Float, comment="伏笔强度 0.0-1.0")
|
|
|
|
|
|
|
|
|
|
# 向量数据库关联
|
|
|
|
|
vector_id = Column(String(100), unique=True, comment="向量数据库中的唯一ID")
|
|
|
|
|
embedding_model = Column(String(100), default="paraphrase-multilingual-MiniLM-L12-v2", comment="使用的embedding模型")
|
|
|
|
|
|
|
|
|
|
created_at = Column(DateTime, server_default=func.now(), comment="创建时间")
|
|
|
|
|
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间")
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return f"<StoryMemory(id={self.id[:8]}, type={self.memory_type}, title={self.title})>"
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
"""转换为字典格式"""
|
|
|
|
|
return {
|
|
|
|
|
"id": self.id,
|
|
|
|
|
"project_id": self.project_id,
|
|
|
|
|
"chapter_id": self.chapter_id,
|
|
|
|
|
"memory_type": self.memory_type,
|
|
|
|
|
"title": self.title,
|
|
|
|
|
"content": self.content,
|
|
|
|
|
"related_characters": self.related_characters,
|
|
|
|
|
"related_locations": self.related_locations,
|
|
|
|
|
"tags": self.tags,
|
|
|
|
|
"importance_score": self.importance_score,
|
|
|
|
|
"story_timeline": self.story_timeline,
|
|
|
|
|
"is_foreshadow": self.is_foreshadow,
|
|
|
|
|
"created_at": self.created_at.isoformat() if self.created_at else None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PlotAnalysis(Base):
|
|
|
|
|
"""剧情分析表 - 存储AI分析的章节结构和剧情元素"""
|
|
|
|
|
__tablename__ = "plot_analysis"
|
|
|
|
|
|
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
|
|
|
project_id = Column(String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
chapter_id = Column(String(36), ForeignKey("chapters.id", ondelete="CASCADE"), nullable=False, unique=True, index=True)
|
|
|
|
|
|
|
|
|
|
# 剧情结构分析
|
|
|
|
|
plot_stage = Column(String(50), comment="剧情阶段: 开端/发展/高潮/结局/过渡")
|
|
|
|
|
conflict_level = Column(Integer, comment="冲突强度 1-10")
|
|
|
|
|
conflict_types = Column(JSON, comment="冲突类型列表: ['人与人', '人与己', '人与环境']")
|
|
|
|
|
|
|
|
|
|
# 情感分析
|
|
|
|
|
emotional_tone = Column(String(100), comment="主导情感: 紧张/温馨/悲伤/激昂/平静")
|
|
|
|
|
emotional_intensity = Column(Float, comment="情感强度 0.0-1.0")
|
|
|
|
|
emotional_curve = Column(JSON, comment="情感曲线: {start: 0.3, middle: 0.7, end: 0.5}")
|
|
|
|
|
|
|
|
|
|
# 钩子分析 (Hook Analysis)
|
|
|
|
|
hooks = Column(JSON, comment="""钩子列表 - 吸引读者的元素: [
|
|
|
|
|
{
|
|
|
|
|
"type": "悬念|情感|冲突|认知",
|
|
|
|
|
"content": "具体内容",
|
|
|
|
|
"strength": 8,
|
|
|
|
|
"position": "开头|中段|结尾"
|
|
|
|
|
}
|
|
|
|
|
]""")
|
|
|
|
|
hooks_count = Column(Integer, default=0, comment="钩子数量")
|
|
|
|
|
hooks_avg_strength = Column(Float, comment="钩子平均强度")
|
|
|
|
|
|
|
|
|
|
# 伏笔分析 (Foreshadowing Analysis)
|
|
|
|
|
foreshadows = Column(JSON, comment="""伏笔列表: [
|
|
|
|
|
{
|
|
|
|
|
"content": "伏笔内容",
|
|
|
|
|
"type": "planted|resolved",
|
|
|
|
|
"strength": 7,
|
|
|
|
|
"subtlety": 8,
|
|
|
|
|
"reference_chapter": 3
|
|
|
|
|
}
|
|
|
|
|
]""")
|
|
|
|
|
foreshadows_planted = Column(Integer, default=0, comment="本章埋下的伏笔数量")
|
|
|
|
|
foreshadows_resolved = Column(Integer, default=0, comment="本章回收的伏笔数量")
|
|
|
|
|
|
|
|
|
|
# 关键情节点 (Plot Points)
|
|
|
|
|
plot_points = Column(JSON, comment="""情节点列表: [
|
|
|
|
|
{
|
|
|
|
|
"content": "情节点描述",
|
|
|
|
|
"importance": 0.9,
|
|
|
|
|
"type": "revelation|conflict|resolution|transition",
|
|
|
|
|
"impact": "对故事的影响描述"
|
|
|
|
|
}
|
|
|
|
|
]""")
|
|
|
|
|
plot_points_count = Column(Integer, default=0, comment="情节点数量")
|
|
|
|
|
|
|
|
|
|
# 角色状态追踪 (Character State Tracking)
|
|
|
|
|
character_states = Column(JSON, comment="""角色状态变化: [
|
|
|
|
|
{
|
|
|
|
|
"character_id": "xxx",
|
|
|
|
|
"character_name": "张三",
|
|
|
|
|
"state_before": "犹豫不决",
|
|
|
|
|
"state_after": "坚定信念",
|
|
|
|
|
"psychological_change": "内心描述",
|
|
|
|
|
"key_event": "触发事件",
|
|
|
|
|
"relationship_changes": {"李四": "关系变化"}
|
|
|
|
|
}
|
|
|
|
|
]""")
|
|
|
|
|
|
|
|
|
|
# 场景和氛围
|
|
|
|
|
scenes = Column(JSON, comment="场景列表: [{location: '地点', atmosphere: '氛围', duration: '时长'}]")
|
|
|
|
|
pacing = Column(String(50), comment="节奏: slow|moderate|fast|varied")
|
|
|
|
|
|
|
|
|
|
# 质量评分
|
|
|
|
|
overall_quality_score = Column(Float, comment="整体质量评分 0.0-10.0")
|
|
|
|
|
pacing_score = Column(Float, comment="节奏评分 0.0-10.0")
|
|
|
|
|
engagement_score = Column(Float, comment="吸引力评分 0.0-10.0")
|
|
|
|
|
coherence_score = Column(Float, comment="连贯性评分 0.0-10.0")
|
|
|
|
|
|
|
|
|
|
# 文本分析报告
|
|
|
|
|
analysis_report = Column(Text, comment="完整的文字分析报告")
|
|
|
|
|
suggestions = Column(JSON, comment="改进建议列表: ['建议1', '建议2']")
|
|
|
|
|
|
|
|
|
|
# 统计信息
|
|
|
|
|
word_count = Column(Integer, comment="章节字数")
|
|
|
|
|
dialogue_ratio = Column(Float, comment="对话占比 0.0-1.0")
|
|
|
|
|
description_ratio = Column(Float, comment="描写占比 0.0-1.0")
|
|
|
|
|
|
|
|
|
|
created_at = Column(DateTime, server_default=func.now(), comment="分析时间")
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return f"<PlotAnalysis(chapter_id={self.chapter_id[:8]}, stage={self.plot_stage}, quality={self.overall_quality_score})>"
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
"""转换为字典格式"""
|
|
|
|
|
return {
|
|
|
|
|
"id": self.id,
|
|
|
|
|
"chapter_id": self.chapter_id,
|
|
|
|
|
"plot_stage": self.plot_stage,
|
|
|
|
|
"conflict_level": self.conflict_level,
|
|
|
|
|
"conflict_types": self.conflict_types or [],
|
|
|
|
|
"emotional_tone": self.emotional_tone,
|
|
|
|
|
"emotional_intensity": self.emotional_intensity or 0.0,
|
|
|
|
|
"hooks": self.hooks or [],
|
|
|
|
|
"hooks_count": self.hooks_count or 0,
|
|
|
|
|
"foreshadows": self.foreshadows or [],
|
|
|
|
|
"foreshadows_planted": self.foreshadows_planted or 0,
|
|
|
|
|
"foreshadows_resolved": self.foreshadows_resolved or 0,
|
|
|
|
|
"plot_points": self.plot_points or [],
|
|
|
|
|
"plot_points_count": self.plot_points_count or 0,
|
|
|
|
|
"character_states": self.character_states or [],
|
|
|
|
|
"scenes": self.scenes or [],
|
|
|
|
|
"pacing": self.pacing,
|
|
|
|
|
"overall_quality_score": self.overall_quality_score or 0.0,
|
|
|
|
|
"pacing_score": self.pacing_score or 0.0,
|
|
|
|
|
"engagement_score": self.engagement_score or 0.0,
|
|
|
|
|
"coherence_score": self.coherence_score or 0.0,
|
|
|
|
|
"analysis_report": self.analysis_report,
|
|
|
|
|
"suggestions": self.suggestions or [],
|
|
|
|
|
"dialogue_ratio": self.dialogue_ratio or 0.0,
|
|
|
|
|
"description_ratio": self.description_ratio or 0.0,
|
|
|
|
|
"created_at": self.created_at.isoformat() if self.created_at else None
|
|
|
|
|
}
|