- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+) - Frontend: Vue 3 + TypeScript + Pinia + Tailwind - Admin Frontend: separate Vue 3 app for management - Docker Compose: 9 services orchestration - Specs: design prototypes, memory system PRD, product roadmap Cleanup performed: - Removed temporary debug scripts from backend root - Removed deprecated admin_app.py (embedded UI) - Removed duplicate docs from admin-frontend - Updated .gitignore for Vite cache and egg-info
6.3 KiB
6.3 KiB
故事宇宙记忆结构
概述
故事宇宙用于在多次故事生成中保持角色、世界观与成长成就的连续性。每个孩子档案可以拥有多个宇宙,故事生成时可选择“延续上一个故事”,系统自动带入宇宙设定。
一、数据库模型
1.1 主表: story_universes
CREATE TABLE story_universes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_profile_id UUID NOT NULL REFERENCES child_profiles(id) ON DELETE CASCADE,
-- 宇宙基础
name VARCHAR(100) NOT NULL,
-- 记忆结构
protagonist JSONB NOT NULL, -- 主角设定
recurring_characters JSONB DEFAULT '[]',
world_settings JSONB DEFAULT '{}',
achievements JSONB DEFAULT '[]',
-- 时间戳
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_story_universes_child_id ON story_universes(child_profile_id);
CREATE INDEX idx_story_universes_updated_at ON story_universes(updated_at);
1.2 JSON 结构示例
protagonist
{
"name": "小明",
"role": "星际船长",
"traits": ["勇敢", "好奇"],
"goal": "寻找失落的星球",
"backstory": "来自地球的探险家"
}
recurring_characters
[
{
"name": "星星",
"role": "魔法猫咪",
"traits": ["聪明", "调皮"],
"relation": "伙伴",
"first_story_id": "story-uuid"
}
]
world_settings
{
"world_name": "梦幻森林",
"era": "童话时代",
"locations": ["彩虹河", "月光山"],
"rules": ["动物会说话", "星星会指路"],
"tone": "温暖治愈"
}
achievements
[
{
"type": "勇气",
"description": "克服了对黑暗的恐惧",
"story_id": "story-uuid",
"achieved_at": "2025-01-10T12:00:00Z"
}
]
二、SQLAlchemy 模型
# backend/app/db/models.py
from sqlalchemy import Column, String, ForeignKey, JSON
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
import uuid
class StoryUniverse(Base):
__tablename__ = "story_universes"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
child_profile_id = Column(UUID(as_uuid=True), ForeignKey("child_profiles.id", ondelete="CASCADE"), nullable=False)
name = Column(String(100), nullable=False)
protagonist = Column(JSON, nullable=False)
recurring_characters = Column(JSON, default=list)
world_settings = Column(JSON, default=dict)
achievements = Column(JSON, default=list)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
child_profile = relationship("ChildProfile", back_populates="story_universes")
三、Pydantic Schema
# backend/app/schemas/story_universe.py
from pydantic import BaseModel, Field
from typing import Any
from uuid import UUID
class StoryUniverseCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
protagonist: dict[str, Any]
recurring_characters: list[dict[str, Any]] = Field(default_factory=list)
world_settings: dict[str, Any] = Field(default_factory=dict)
class StoryUniverseUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=100)
protagonist: dict[str, Any] | None = None
recurring_characters: list[dict[str, Any]] | None = None
world_settings: dict[str, Any] | None = None
class StoryUniverseResponse(BaseModel):
id: UUID
child_profile_id: UUID
name: str
protagonist: dict[str, Any]
recurring_characters: list[dict[str, Any]]
world_settings: dict[str, Any]
achievements: list[dict[str, Any]]
class Config:
from_attributes = True
四、API 约定
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/profiles/{id}/universes |
获取孩子的故事宇宙列表 |
| POST | /api/profiles/{id}/universes |
创建新宇宙 |
| GET | /api/universes/{id} |
获取宇宙详情 |
| PUT | /api/universes/{id} |
更新宇宙设定 |
| POST | /api/universes/{id}/achievements |
添加成就 |
五、业务规则
- 延续故事: “延续上一个故事”默认选最近更新的宇宙(按
updated_at倒序)。 - 成就追加: 新成就追加到
achievements,以type + description去重。 - 成长轨迹: 成就保留顺序,优先展示最新项。
六、Prompt 集成
当选择宇宙时,生成 Prompt 需带入宇宙记忆:
【故事宇宙】
- 主角设定: {protagonist}
- 常驻角色: {recurring_characters}
- 世界观: {world_settings}
- 已获成就: {achievements}
未选择宇宙时,提示词忽略该块,避免混淆。
七、数据迁移示例
# backend/alembic/versions/xxx_add_story_universes.py
def upgrade():
op.create_table(
"story_universes",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("child_profile_id", sa.UUID(), nullable=False),
sa.Column("name", sa.String(100), nullable=False),
sa.Column("protagonist", sa.JSON(), nullable=False),
sa.Column("recurring_characters", sa.JSON(), server_default='[]'),
sa.Column("world_settings", sa.JSON(), server_default='{}'),
sa.Column("achievements", sa.JSON(), server_default='[]'),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["child_profile_id"], ["child_profiles.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("idx_story_universes_child_id", "story_universes", ["child_profile_id"])
op.create_index("idx_story_universes_updated_at", "story_universes", ["updated_at"])
def downgrade():
op.drop_index("idx_story_universes_updated_at")
op.drop_index("idx_story_universes_child_id")
op.drop_table("story_universes")
八、权限与安全
- 宇宙数据必须通过
child_profile_id归属校验,确保仅拥有者可访问。 - 删除用户或档案时,级联删除所有宇宙数据。