Files
dreamweaver/.claude/specs/memory-intelligence/STORY-UNIVERSE-MODEL.md
zhangtuo e9d7f8832a Initial commit: clean project structure
- 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
2026-01-20 18:20:03 +08:00

232 lines
6.3 KiB
Markdown

# 故事宇宙记忆结构
## 概述
故事宇宙用于在多次故事生成中保持角色、世界观与成长成就的连续性。每个孩子档案可以拥有多个宇宙,故事生成时可选择“延续上一个故事”,系统自动带入宇宙设定。
---
## 一、数据库模型
### 1.1 主表: story_universes
```sql
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**
```json
{
"name": "小明",
"role": "星际船长",
"traits": ["勇敢", "好奇"],
"goal": "寻找失落的星球",
"backstory": "来自地球的探险家"
}
```
**recurring_characters**
```json
[
{
"name": "星星",
"role": "魔法猫咪",
"traits": ["聪明", "调皮"],
"relation": "伙伴",
"first_story_id": "story-uuid"
}
]
```
**world_settings**
```json
{
"world_name": "梦幻森林",
"era": "童话时代",
"locations": ["彩虹河", "月光山"],
"rules": ["动物会说话", "星星会指路"],
"tone": "温暖治愈"
}
```
**achievements**
```json
[
{
"type": "勇气",
"description": "克服了对黑暗的恐惧",
"story_id": "story-uuid",
"achieved_at": "2025-01-10T12:00:00Z"
}
]
```
---
## 二、SQLAlchemy 模型
```python
# 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
```python
# 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}
```
未选择宇宙时,提示词忽略该块,避免混淆。
---
## 七、数据迁移示例
```python
# 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` 归属校验,确保仅拥有者可访问。
- 删除用户或档案时,级联删除所有宇宙数据。
---
## 九、相关文档
- [孩子档案数据模型](./CHILD-PROFILE-MODEL.md)
- [记忆智能系统 PRD](./MEMORY-INTELLIGENCE-PRD.md)