192 lines
6.1 KiB
Python
192 lines
6.1 KiB
Python
"""API endpoints for file system-based summary management."""
|
|
|
|
from fastapi import APIRouter, HTTPException, Path, Query
|
|
from pydantic import BaseModel, Field
|
|
from typing import List, Dict, Any, Optional
|
|
import logging
|
|
|
|
from ..services.summary_storage import storage_service
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/summaries", tags=["file-summaries"])
|
|
|
|
|
|
class SummaryResponse(BaseModel):
|
|
"""Response model for a single summary."""
|
|
video_id: str
|
|
generated_at: str
|
|
model: str
|
|
summary: str
|
|
key_points: List[str]
|
|
main_themes: List[str]
|
|
actionable_insights: List[str]
|
|
confidence_score: float
|
|
processing_metadata: Dict[str, Any]
|
|
cost_data: Dict[str, Any]
|
|
transcript_length: int
|
|
file_path: str
|
|
file_size_bytes: int
|
|
file_created_at: str
|
|
file_modified_at: str
|
|
|
|
|
|
class SummaryListResponse(BaseModel):
|
|
"""Response model for multiple summaries."""
|
|
video_id: str
|
|
summaries: List[SummaryResponse]
|
|
total_summaries: int
|
|
|
|
|
|
class SummaryStatsResponse(BaseModel):
|
|
"""Response model for summary statistics."""
|
|
total_videos_with_summaries: int
|
|
total_summaries: int
|
|
total_size_bytes: int
|
|
total_size_mb: float
|
|
model_distribution: Dict[str, int]
|
|
video_ids: List[str]
|
|
|
|
|
|
@router.get("/video/{video_id}", response_model=SummaryListResponse)
|
|
async def get_video_summaries(
|
|
video_id: str = Path(..., description="YouTube video ID"),
|
|
):
|
|
"""Get all summaries for a specific video."""
|
|
try:
|
|
summaries_data = storage_service.list_summaries(video_id)
|
|
|
|
# Convert to Pydantic models
|
|
summaries = [SummaryResponse(**summary) for summary in summaries_data]
|
|
|
|
return SummaryListResponse(
|
|
video_id=video_id,
|
|
summaries=summaries,
|
|
total_summaries=len(summaries)
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get summaries for video {video_id}: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to retrieve summaries: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/video/{video_id}/{timestamp}", response_model=SummaryResponse)
|
|
async def get_specific_summary(
|
|
video_id: str = Path(..., description="YouTube video ID"),
|
|
timestamp: str = Path(..., description="Summary timestamp")
|
|
):
|
|
"""Get a specific summary by video ID and timestamp."""
|
|
try:
|
|
summary_data = storage_service.get_summary(video_id, timestamp)
|
|
|
|
if not summary_data:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Summary not found for video {video_id} with timestamp {timestamp}"
|
|
)
|
|
|
|
return SummaryResponse(**summary_data)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Failed to get summary {video_id}/{timestamp}: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to retrieve summary: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/stats", response_model=SummaryStatsResponse)
|
|
async def get_summary_stats():
|
|
"""Get statistics about all stored summaries."""
|
|
try:
|
|
stats = storage_service.get_summary_stats()
|
|
return SummaryStatsResponse(**stats)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get summary stats: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to retrieve statistics: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/videos", response_model=List[str])
|
|
async def list_videos_with_summaries():
|
|
"""Get list of video IDs that have summaries."""
|
|
try:
|
|
video_ids = storage_service.get_videos_with_summaries()
|
|
return video_ids
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to list videos with summaries: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to retrieve video list: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.delete("/video/{video_id}/{timestamp}")
|
|
async def delete_summary(
|
|
video_id: str = Path(..., description="YouTube video ID"),
|
|
timestamp: str = Path(..., description="Summary timestamp")
|
|
):
|
|
"""Delete a specific summary."""
|
|
try:
|
|
success = storage_service.delete_summary(video_id, timestamp)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Summary not found for video {video_id} with timestamp {timestamp}"
|
|
)
|
|
|
|
return {"message": f"Summary deleted successfully", "video_id": video_id, "timestamp": timestamp}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete summary {video_id}/{timestamp}: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to delete summary: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.post("/video/{video_id}/generate")
|
|
async def trigger_summary_generation(
|
|
video_id: str = Path(..., description="YouTube video ID"),
|
|
force: bool = Query(False, description="Force regeneration even if summaries exist")
|
|
):
|
|
"""Trigger summary generation for a video."""
|
|
try:
|
|
# Check if summaries already exist
|
|
existing_summaries = storage_service.list_summaries(video_id)
|
|
|
|
if existing_summaries and not force:
|
|
return {
|
|
"message": "Summaries already exist for this video",
|
|
"video_id": video_id,
|
|
"existing_summaries": len(existing_summaries),
|
|
"use_force_parameter": "Set force=true to regenerate"
|
|
}
|
|
|
|
# TODO: Integrate with actual summary generation pipeline
|
|
# For now, return a placeholder response
|
|
return {
|
|
"message": "Summary generation would be triggered here",
|
|
"video_id": video_id,
|
|
"force": force,
|
|
"note": "This endpoint will be connected to the DeepSeek summarization pipeline"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to trigger summary generation for {video_id}: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to trigger summary generation: {str(e)}"
|
|
) |