- 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
232 lines
6.3 KiB
Markdown
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)
|