feat: add week 3 audio and timeline enhancements

This commit is contained in:
2026-04-18 22:10:48 +08:00
parent 4d54c144a8
commit 70efaf3ccf
20 changed files with 606 additions and 56 deletions

View File

@@ -10,7 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import require_user
from app.db.database import get_db
from app.db.models import ChildProfile, Story, StoryUniverse, User
from app.db.models import ChildProfile, MemoryItem, ReadingEvent, Story, StoryUniverse, User
router = APIRouter()
@@ -67,7 +67,7 @@ class TimelineEvent(BaseModel):
"""Timeline event item."""
date: str
type: Literal["story", "achievement", "milestone"]
type: Literal["story", "achievement", "milestone", "reading_event", "memory"]
title: str
description: str | None = None
image_url: str | None = None
@@ -241,7 +241,10 @@ async def get_profile_timeline(
# 2. Stories
stories_result = await db.execute(
select(Story).where(Story.child_profile_id == profile_id)
select(Story).where(
Story.child_profile_id == profile_id,
Story.user_id == user.id,
)
)
for s in stories_result.scalars():
events.append(TimelineEvent(
@@ -252,7 +255,89 @@ async def get_profile_timeline(
metadata={"story_id": s.id, "mode": s.mode}
))
# 3. Achievements (from Universe)
# 3. Reading events
reading_result = await db.execute(
select(ReadingEvent, Story)
.outerjoin(Story, ReadingEvent.story_id == Story.id)
.where(ReadingEvent.child_profile_id == profile_id)
.order_by(ReadingEvent.created_at.desc())
)
reading_labels = {
"started": "开始阅读",
"completed": "读完故事",
"replayed": "再次阅读",
"skipped": "暂时跳过",
}
for reading_event, story in reading_result.all():
story_title = story.title if story else "未关联故事"
duration = (
f"阅读 {reading_event.reading_time}"
if reading_event.reading_time
else "记录了一次阅读行为"
)
events.append(
TimelineEvent(
date=reading_event.created_at.isoformat(),
type="reading_event",
title=reading_labels.get(reading_event.event_type, "阅读记录"),
description=f"{story_title} · {duration}",
image_url=story.image_url if story else None,
metadata={
"story_id": reading_event.story_id,
"mode": story.mode if story else None,
"event_type": reading_event.event_type,
"reading_time": reading_event.reading_time,
},
)
)
# 4. Memory items
memories_result = await db.execute(
select(MemoryItem)
.where(MemoryItem.child_profile_id == profile_id)
.order_by(MemoryItem.created_at.desc())
.limit(20)
)
memory_labels = {
"recent_story": "近期故事记忆",
"favorite_character": "喜欢的角色",
"scary_element": "回避元素",
"vocabulary_growth": "词汇积累",
"emotional_highlight": "情感高光",
"reading_preference": "阅读偏好",
"milestone": "成长里程碑",
"skill_mastered": "掌握的技能",
}
for memory in memories_result.scalars():
value = memory.value or {}
title = str(
value.get("title")
or value.get("name")
or value.get("keyword")
or value.get("description")
or memory_labels.get(memory.type, memory.type)
)
events.append(
TimelineEvent(
date=memory.created_at.isoformat(),
type="memory",
title=f"记忆沉淀:{memory_labels.get(memory.type, memory.type)}",
description=title,
image_url=(
value.get("image_url")
if isinstance(value.get("image_url"), str)
else None
),
metadata={
"memory_id": memory.id,
"memory_type": memory.type,
"story_id": value.get("story_id") or value.get("source_story_id"),
"mode": value.get("mode"),
},
)
)
# 5. Achievements (from Universe)
universes_result = await db.execute(
select(StoryUniverse).where(StoryUniverse.child_profile_id == profile_id)
)