from functools import lru_cache from typing import Annotated from fastapi import Depends, FastAPI, Query, Request from fastapi.responses import JSONResponse import gradio as gr from pydantic import ValidationError from app.adapters.openai import OpenAIAdapter from app.configurations import EnvConfigs from app.mappers import to_batch_transcript_analysis_response, to_transcript_analysis_response from app.schemas import ( BatchTranscriptAnalysisRequest, BatchTranscriptAnalysisResponse, TranscriptAnalysisResponse, ) from app.errors import ( AnalysisNotFoundError, ConfigurationError, InvalidTranscriptError, LLMCompletionError, ) from app.frontend import APP_CSS, build_gradio_app from app.ports import LLm from app.repositories import InMemoryTranscriptAnalysisRepository, TranscriptAnalysisRepository from app.services import TranscriptAnalysisService app = FastAPI( title="Transcript Analysis API", version="0.1.0", description="Analyze plain text transcripts and retrieve stored analysis results.", ) _repository = InMemoryTranscriptAnalysisRepository() @lru_cache def get_env_configs() -> EnvConfigs: try: return EnvConfigs() except ValidationError as exc: raise ConfigurationError( "OPENAI_API_KEY is not configured. Set it in .env or as an environment variable." ) from exc @lru_cache def get_llm() -> LLm: env_configs = get_env_configs() return OpenAIAdapter(env_configs.OPENAI_API_KEY, env_configs.OPENAI_MODEL) def get_repository() -> TranscriptAnalysisRepository: return _repository def get_analysis_service( llm: Annotated[LLm, Depends(get_llm)], repository: Annotated[TranscriptAnalysisRepository, Depends(get_repository)], ) -> TranscriptAnalysisService: return TranscriptAnalysisService(llm, repository) def get_gradio_analysis_service() -> TranscriptAnalysisService: return TranscriptAnalysisService(get_llm(), get_repository()) @app.exception_handler(InvalidTranscriptError) async def invalid_transcript_handler(_: Request, exc: InvalidTranscriptError) -> JSONResponse: return JSONResponse(status_code=400, content={"detail": str(exc)}) @app.exception_handler(AnalysisNotFoundError) async def analysis_not_found_handler(_: Request, exc: AnalysisNotFoundError) -> JSONResponse: return JSONResponse(status_code=404, content={"detail": str(exc)}) @app.exception_handler(ConfigurationError) async def configuration_error_handler(_: Request, exc: ConfigurationError) -> JSONResponse: return JSONResponse(status_code=503, content={"detail": str(exc)}) @app.exception_handler(LLMCompletionError) async def llm_completion_handler(_: Request, exc: LLMCompletionError) -> JSONResponse: return JSONResponse(status_code=502, content={"detail": str(exc)}) @app.get("/analyses", response_model=TranscriptAnalysisResponse) def analyze_transcript( transcript: Annotated[str, Query(description="Plain text transcript to analyze.")], service: Annotated[TranscriptAnalysisService, Depends(get_analysis_service)], ) -> TranscriptAnalysisResponse: analysis = service.analyze(transcript) return to_transcript_analysis_response(analysis) @app.get("/analyses/{analysis_id}", response_model=TranscriptAnalysisResponse) def get_transcript_analysis( analysis_id: str, service: Annotated[TranscriptAnalysisService, Depends(get_analysis_service)], ) -> TranscriptAnalysisResponse: analysis = service.get(analysis_id) return to_transcript_analysis_response(analysis) @app.post("/analyses/batch", response_model=BatchTranscriptAnalysisResponse) async def analyze_transcripts_batch( request: BatchTranscriptAnalysisRequest, service: Annotated[TranscriptAnalysisService, Depends(get_analysis_service)], ) -> BatchTranscriptAnalysisResponse: analyses = await service.analyze_many(request.transcripts) return to_batch_transcript_analysis_response(analyses) app = gr.mount_gradio_app( app, build_gradio_app(get_gradio_analysis_service), path="/", css=APP_CSS, )