youtube-summarizer/backend/api/enhanced_export.py

474 lines
16 KiB
Python

"""Enhanced Export API endpoints for Story 4.4."""
import asyncio
import logging
from datetime import datetime
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks, Query
from pydantic import BaseModel, Field
import uuid
from ..services.executive_summary_generator import ExecutiveSummaryGenerator
from ..services.timestamp_processor import TimestampProcessor
from ..services.enhanced_markdown_formatter import EnhancedMarkdownFormatter, MarkdownExportConfig
from ..services.enhanced_template_manager import EnhancedTemplateManager, DomainCategory, PromptTemplate
from ..core.dependencies import get_current_user
from ..models.user import User
from ..core.exceptions import ServiceError
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/export", tags=["Enhanced Export"])
# Initialize services
executive_generator = ExecutiveSummaryGenerator()
timestamp_processor = TimestampProcessor()
markdown_formatter = EnhancedMarkdownFormatter(executive_generator, timestamp_processor)
template_manager = EnhancedTemplateManager()
# Request/Response Models
class EnhancedExportRequest(BaseModel):
"""Request model for enhanced export generation."""
summary_id: str
export_config: Optional[Dict[str, Any]] = None
template_id: Optional[str] = None
format: str = Field(default="markdown", description="Export format (markdown, pdf, html)")
include_executive_summary: bool = True
include_timestamps: bool = True
include_toc: bool = True
section_detail_level: str = Field(default="standard", description="brief, standard, detailed")
class ExportConfigResponse(BaseModel):
"""Available export configuration options."""
available_formats: List[str]
section_detail_levels: List[str]
default_config: Dict[str, Any]
class EnhancedExportResponse(BaseModel):
"""Response model for enhanced export."""
export_id: str
summary_id: str
export_format: str
content: str
metadata: Dict[str, Any]
quality_score: float
processing_time_seconds: float
created_at: str
config_used: Dict[str, Any]
class TemplateCreateRequest(BaseModel):
"""Request model for creating custom templates."""
name: str
description: str
prompt_text: str
domain_category: DomainCategory
model_config: Optional[Dict[str, Any]] = None
is_public: bool = False
tags: Optional[List[str]] = None
class TemplateResponse(BaseModel):
"""Response model for template data."""
id: str
name: str
description: str
domain_category: str
is_public: bool
usage_count: int
rating: float
version: str
created_at: str
tags: List[str]
class TemplateExecuteRequest(BaseModel):
"""Request model for executing a template."""
template_id: str
variables: Dict[str, Any]
override_config: Optional[Dict[str, Any]] = None
# Enhanced Export Endpoints
@router.post("/enhanced", response_model=EnhancedExportResponse)
async def generate_enhanced_export(
request: EnhancedExportRequest,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user)
):
"""Generate enhanced markdown export with executive summary and timestamped sections."""
try:
# TODO: Get summary data from database using summary_id
# For now, using placeholder data
video_title = "Sample Video Title"
video_url = "https://youtube.com/watch?v=sample"
content = "This is sample content for enhanced export generation."
transcript_data = [] # TODO: Get real transcript data
# Create export configuration
export_config = MarkdownExportConfig(
include_executive_summary=request.include_executive_summary,
include_timestamps=request.include_timestamps,
include_toc=request.include_toc,
section_detail_level=request.section_detail_level,
custom_template_id=request.template_id
)
# Generate enhanced export
export_result = await markdown_formatter.create_enhanced_export(
video_title=video_title,
video_url=video_url,
content=content,
transcript_data=transcript_data,
export_config=export_config
)
# TODO: Save export metadata to database
export_id = str(uuid.uuid4())
# Background task: Update template usage statistics
if request.template_id:
background_tasks.add_task(
_update_template_usage_stats,
request.template_id,
export_result.processing_time_seconds,
len(export_result.markdown_content)
)
return EnhancedExportResponse(
export_id=export_id,
summary_id=request.summary_id,
export_format=request.format,
content=export_result.markdown_content,
metadata=export_result.metadata,
quality_score=export_result.quality_score,
processing_time_seconds=export_result.processing_time_seconds,
created_at=export_result.created_at.isoformat(),
config_used=request.dict()
)
except ServiceError as e:
logger.error(f"Enhanced export generation failed: {e}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error in enhanced export: {e}")
raise HTTPException(status_code=500, detail="Export generation failed")
@router.get("/config", response_model=ExportConfigResponse)
async def get_export_config():
"""Get available export configuration options."""
return ExportConfigResponse(
available_formats=["markdown", "pdf", "html", "json"],
section_detail_levels=["brief", "standard", "detailed"],
default_config={
"include_executive_summary": True,
"include_timestamps": True,
"include_toc": True,
"section_detail_level": "standard",
"format": "markdown"
}
)
@router.get("/{export_id}/download")
async def download_export(
export_id: str,
current_user: User = Depends(get_current_user)
):
"""Download a previously generated export."""
# TODO: Implement export download from storage
# For now, return placeholder response
raise HTTPException(status_code=501, detail="Export download not yet implemented")
# Template Management Endpoints
@router.post("/templates", response_model=TemplateResponse)
async def create_template(
request: TemplateCreateRequest,
current_user: User = Depends(get_current_user)
):
"""Create a custom prompt template."""
try:
template = await template_manager.create_template(
name=request.name,
description=request.description,
prompt_text=request.prompt_text,
domain_category=request.domain_category,
model_config=None, # Will use defaults
is_public=request.is_public,
created_by=current_user.id,
tags=request.tags or []
)
return TemplateResponse(
id=template.id,
name=template.name,
description=template.description,
domain_category=template.domain_category.value,
is_public=template.is_public,
usage_count=template.usage_count,
rating=template.rating,
version=template.version,
created_at=template.created_at.isoformat(),
tags=template.tags
)
except ServiceError as e:
logger.error(f"Template creation failed: {e}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error creating template: {e}")
raise HTTPException(status_code=500, detail="Template creation failed")
@router.get("/templates", response_model=List[TemplateResponse])
async def list_templates(
domain_category: Optional[DomainCategory] = Query(None),
is_public: Optional[bool] = Query(None),
current_user: User = Depends(get_current_user)
):
"""List available prompt templates."""
try:
templates = await template_manager.list_templates(
domain_category=domain_category,
is_public=is_public,
created_by=current_user.id if is_public is False else None
)
return [
TemplateResponse(
id=template.id,
name=template.name,
description=template.description,
domain_category=template.domain_category.value,
is_public=template.is_public,
usage_count=template.usage_count,
rating=template.rating,
version=template.version,
created_at=template.created_at.isoformat(),
tags=template.tags
)
for template in templates
]
except Exception as e:
logger.error(f"Error listing templates: {e}")
raise HTTPException(status_code=500, detail="Failed to list templates")
@router.get("/templates/{template_id}", response_model=TemplateResponse)
async def get_template(
template_id: str,
current_user: User = Depends(get_current_user)
):
"""Get a specific template by ID."""
try:
template = await template_manager.get_template(template_id)
if not template:
raise HTTPException(status_code=404, detail="Template not found")
# Check permissions
if not template.is_public and template.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
return TemplateResponse(
id=template.id,
name=template.name,
description=template.description,
domain_category=template.domain_category.value,
is_public=template.is_public,
usage_count=template.usage_count,
rating=template.rating,
version=template.version,
created_at=template.created_at.isoformat(),
tags=template.tags
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting template: {e}")
raise HTTPException(status_code=500, detail="Failed to get template")
@router.post("/templates/{template_id}/execute")
async def execute_template(
template_id: str,
request: TemplateExecuteRequest,
current_user: User = Depends(get_current_user)
):
"""Execute a template with provided variables."""
try:
result = await template_manager.execute_template(
template_id=template_id,
variables=request.variables,
override_config=None
)
return {
"template_id": template_id,
"execution_result": result,
"executed_at": datetime.now().isoformat(),
"user_id": current_user.id
}
except ServiceError as e:
logger.error(f"Template execution failed: {e}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error executing template: {e}")
raise HTTPException(status_code=500, detail="Template execution failed")
@router.delete("/templates/{template_id}")
async def delete_template(
template_id: str,
current_user: User = Depends(get_current_user)
):
"""Delete a custom template."""
try:
template = await template_manager.get_template(template_id)
if not template:
raise HTTPException(status_code=404, detail="Template not found")
# Check permissions
if template.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Can only delete your own templates")
success = await template_manager.delete_template(template_id)
if success:
return {"message": "Template deleted successfully", "template_id": template_id}
else:
raise HTTPException(status_code=404, detail="Template not found")
except HTTPException:
raise
except ServiceError as e:
logger.error(f"Template deletion failed: {e}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error deleting template: {e}")
raise HTTPException(status_code=500, detail="Template deletion failed")
# Domain-Specific Recommendations
@router.post("/recommendations")
async def get_domain_recommendations(
content_sample: str = Query(..., description="Sample content for analysis"),
max_recommendations: int = Query(3, description="Maximum number of recommendations")
):
"""Get domain template recommendations based on content."""
try:
recommendations = await template_manager.get_domain_recommendations(
content_sample=content_sample,
max_recommendations=max_recommendations
)
return {
"content_analyzed": content_sample[:100] + "..." if len(content_sample) > 100 else content_sample,
"recommendations": recommendations,
"generated_at": datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Error getting recommendations: {e}")
raise HTTPException(status_code=500, detail="Failed to get recommendations")
# Analytics and Statistics
@router.get("/templates/{template_id}/analytics")
async def get_template_analytics(
template_id: str,
current_user: User = Depends(get_current_user)
):
"""Get analytics for a specific template."""
try:
analytics = await template_manager.get_template_analytics(template_id)
return analytics
except ServiceError as e:
logger.error(f"Template analytics failed: {e}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error getting template analytics: {e}")
raise HTTPException(status_code=500, detail="Failed to get analytics")
@router.get("/system/stats")
async def get_system_stats():
"""Get overall system statistics."""
try:
stats = await template_manager.get_system_stats()
return stats
except Exception as e:
logger.error(f"Error getting system stats: {e}")
raise HTTPException(status_code=500, detail="Failed to get system stats")
# Background task helpers
async def _update_template_usage_stats(
template_id: str,
processing_time: float,
response_length: int
):
"""Background task to update template usage statistics."""
try:
await template_manager._update_template_usage(
template_id, processing_time, response_length
)
except Exception as e:
logger.error(f"Failed to update template usage stats: {e}")
# Health check
@router.get("/health")
async def health_check():
"""Enhanced export service health check."""
try:
# Test service availability
executive_stats = executive_generator.get_executive_summary_stats()
timestamp_stats = timestamp_processor.get_processor_stats()
formatter_stats = markdown_formatter.get_formatter_stats()
system_stats = await template_manager.get_system_stats()
return {
"status": "healthy",
"services": {
"executive_summary_generator": executive_stats,
"timestamp_processor": timestamp_stats,
"markdown_formatter": formatter_stats,
"template_manager": system_stats
},
"timestamp": datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(status_code=503, detail="Service unhealthy")