- 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
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
"""Celery tasks for achievements."""
|
|
|
|
import asyncio
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.core.celery_app import celery_app
|
|
from app.core.logging import get_logger
|
|
from app.db.database import _get_session_factory
|
|
from app.db.models import Story, StoryUniverse
|
|
from app.services.achievement_extractor import extract_achievements
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
@celery_app.task
|
|
def extract_story_achievements(story_id: int, universe_id: str) -> None:
|
|
"""Extract achievements and update universe."""
|
|
asyncio.run(_extract_story_achievements(story_id, universe_id))
|
|
|
|
|
|
async def _extract_story_achievements(story_id: int, universe_id: str) -> None:
|
|
session_factory = _get_session_factory()
|
|
async with session_factory() as session:
|
|
result = await session.execute(select(Story).where(Story.id == story_id))
|
|
story = result.scalar_one_or_none()
|
|
if not story:
|
|
logger.warning("achievement_task_story_missing", story_id=story_id)
|
|
return
|
|
|
|
result = await session.execute(
|
|
select(StoryUniverse).where(StoryUniverse.id == universe_id)
|
|
)
|
|
universe = result.scalar_one_or_none()
|
|
if not universe:
|
|
logger.warning("achievement_task_universe_missing", universe_id=universe_id)
|
|
return
|
|
|
|
text_content = story.story_text
|
|
if not text_content and story.pages:
|
|
# 如果是绘本,拼接每页文本
|
|
text_content = "\n".join([str(p.get("text", "")) for p in story.pages])
|
|
|
|
if not text_content:
|
|
logger.warning("achievement_task_empty_content", story_id=story_id)
|
|
return
|
|
|
|
achievements = await extract_achievements(text_content)
|
|
if not achievements:
|
|
logger.info("achievement_task_no_new", story_id=story_id)
|
|
return
|
|
|
|
existing = {
|
|
(str(item.get("type", "")).strip(), str(item.get("description", "")).strip())
|
|
for item in (universe.achievements or [])
|
|
if isinstance(item, dict)
|
|
}
|
|
merged = list(universe.achievements or [])
|
|
added_count = 0
|
|
|
|
for item in achievements:
|
|
key = (item.get("type", "").strip(), item.get("description", "").strip())
|
|
if key in existing:
|
|
continue
|
|
merged.append({
|
|
"type": key[0],
|
|
"description": key[1],
|
|
"obtained_at": datetime.now().isoformat(),
|
|
"source_story_id": story_id,
|
|
})
|
|
existing.add(key)
|
|
added_count += 1
|
|
|
|
universe.achievements = merged
|
|
await session.commit()
|
|
logger.info(
|
|
"achievement_task_success",
|
|
story_id=story_id,
|
|
universe_id=universe_id,
|
|
added=added_count,
|
|
)
|