diff --git a/api/app/domain/services/adventure_service.py b/api/app/domain/services/adventure_service.py index 11cd986..0610be5 100644 --- a/api/app/domain/services/adventure_service.py +++ b/api/app/domain/services/adventure_service.py @@ -192,7 +192,7 @@ class AdventureService: max_tokens=2048, ) - story_text, choices_parsed, gm_notes = parse_entry_response(raw_text) + story_text, choices_parsed, gm_notes, story_so_far = parse_entry_response(raw_text) await self.entry_repo.update_content( entry_id=entry_id, @@ -222,7 +222,8 @@ class AdventureService: ) voice = self.gemini_client.get_voice_by_language(adventure.language) - wav_bytes = await self.gemini_client.generate_audio(story_text, voice) + story_text_with_tag = "[like a dungeons and dragons gamemaster] " + story_text + wav_bytes = await self.gemini_client.generate_audio(story_text_with_tag, voice) audio_key = f"adventure-audio/{entry_id}.wav" upload_audio(audio_key, wav_bytes) await self.audio_repo.create( diff --git a/api/app/outbound/anthropic/adventure_prompts.py b/api/app/outbound/anthropic/adventure_prompts.py index 851a286..a314b7c 100644 --- a/api/app/outbound/anthropic/adventure_prompts.py +++ b/api/app/outbound/anthropic/adventure_prompts.py @@ -35,7 +35,7 @@ def build_entry_system_prompt( f"only \"-----\".\n" f"- Part 1: the story entry, {min_length}–{max_length} words, speaking directly to the player.\n" f"- Part 2: exactly 4 numbered player options, one per line, labelled \"1.\", \"2.\", \"3.\", \"4.\".\n" - f"- Part 3: GM notes to your future self (hidden from the player). " + f"- Part 3: GM notes to your future self (hidden from the player). \n" f"If no notes, write \"no notes\".\n" f"- Your first message must establish: who the player is, the setting, and the broad direction.\n" f"- No sexual content or graphic violence. Romance, threat, and adventure are fine (12-certificate)." diff --git a/api/app/routers/media.py b/api/app/routers/media.py index 21ea447..47e7986 100644 --- a/api/app/routers/media.py +++ b/api/app/routers/media.py @@ -1,3 +1,5 @@ +import uuid + from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import Response from sqlalchemy.ext.asyncio import AsyncSession @@ -5,11 +7,37 @@ from botocore.exceptions import ClientError from ..outbound.postgres.database import get_db from ..outbound.postgres.repositories.translated_article_repository import TranslatedArticleRepository +from ..outbound.postgres.repositories.adventure_repository import PostgresAdventureEntryAudioRepository from ..storage import download_audio router = APIRouter(prefix="/media", tags=["media"]) +@router.get("/adventure-audio/{filename:path}") +async def get_adventure_audio_file( + filename: str, + db: AsyncSession = Depends(get_db), +) -> Response: + try: + eid = uuid.UUID(filename.rsplit(".", 1)[0]) + except ValueError: + raise HTTPException(status_code=400, detail="Invalid file ID") + + print(f"Looking for adventure audio with entry ID: {eid}") + + adventure_audio = await PostgresAdventureEntryAudioRepository(db).get_for_entry(entry_id=eid, component_type="story_text") + + if adventure_audio is None: + raise HTTPException(status_code=404, detail="File not found") + try: + audio_bytes, content_type = download_audio("adventure-audio/" + filename) + except ClientError as e: + if e.response["Error"]["Code"] in ("NoSuchKey", "404"): + raise HTTPException(status_code=404, detail="File not found") + raise HTTPException(status_code=500, detail="Storage error") + + return Response(content=audio_bytes, media_type=content_type) + @router.get("/{filename:path}") async def get_media_file( filename: str, @@ -28,3 +56,4 @@ async def get_media_file( raise HTTPException(status_code=500, detail="Storage error") return Response(content=audio_bytes, media_type=content_type) +