AI Image Studio, AI podcast Maker, AI product Marketing

This commit is contained in:
ajaysi
2025-11-28 14:33:52 +05:30
parent 77d7c0cde6
commit 49e2131715
122 changed files with 22311 additions and 4331 deletions

View File

@@ -10,6 +10,9 @@ from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
from loguru import logger
from middleware.auth_middleware import get_current_user
from sqlalchemy.orm import Session
from services.database import get_db as get_db_dependency
from utils.text_asset_tracker import save_and_track_text_content
from models.blog_models import (
BlogResearchRequest,
@@ -41,6 +44,10 @@ router = APIRouter(prefix="/api/blog", tags=["AI Blog Writer"])
service = BlogWriterService()
recommendation_applier = BlogSEORecommendationApplier()
# Use the proper database dependency from services.database
get_db = get_db_dependency
# ---------------------------
# SEO Recommendation Endpoints
# ---------------------------
@@ -272,10 +279,41 @@ async def rebalance_outline(outline_data: Dict[str, Any], target_words: int = 15
# Content Generation Endpoints
@router.post("/section/generate", response_model=BlogSectionResponse)
async def generate_section(request: BlogSectionRequest) -> BlogSectionResponse:
async def generate_section(
request: BlogSectionRequest,
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db)
) -> BlogSectionResponse:
"""Generate content for a specific section."""
try:
return await service.generate_section(request)
response = await service.generate_section(request)
# Save and track text content (non-blocking)
if response.markdown:
try:
user_id = str(current_user.get('id', '')) if current_user else None
if user_id:
section_heading = getattr(request, 'section_heading', getattr(request, 'heading', 'Section'))
save_and_track_text_content(
db=db,
user_id=user_id,
content=response.markdown,
source_module="blog_writer",
title=f"Blog Section: {section_heading[:60]}",
description=f"Blog section content",
prompt=f"Section: {section_heading}\nKeywords: {getattr(request, 'keywords', [])}",
tags=["blog", "section", "content"],
asset_metadata={
"section_id": getattr(request, 'section_id', None),
"word_count": len(response.markdown.split()),
},
subdirectory="sections",
file_extension=".md"
)
except Exception as track_error:
logger.warning(f"Failed to track blog section asset: {track_error}")
return response
except Exception as e:
logger.error(f"Failed to generate section: {e}")
raise HTTPException(status_code=500, detail=str(e))
@@ -321,13 +359,48 @@ async def start_content_generation(
@router.get("/content/status/{task_id}")
async def content_generation_status(task_id: str) -> Dict[str, Any]:
async def content_generation_status(
task_id: str,
current_user: Optional[Dict[str, Any]] = Depends(get_current_user),
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Poll status for content generation task."""
try:
status = await task_manager.get_task_status(task_id)
if status is None:
raise HTTPException(status_code=404, detail="Task not found")
# Track blog content when task completes (non-blocking)
if status.get('status') == 'completed' and status.get('result'):
try:
result = status.get('result', {})
if result.get('sections') and len(result.get('sections', [])) > 0:
user_id = str(current_user.get('id', '')) if current_user else None
if user_id:
# Combine all sections into full blog content
blog_content = f"# {result.get('title', 'Untitled Blog')}\n\n"
for section in result.get('sections', []):
blog_content += f"\n## {section.get('heading', 'Section')}\n\n{section.get('content', '')}\n\n"
save_and_track_text_content(
db=db,
user_id=user_id,
content=blog_content,
source_module="blog_writer",
title=f"Blog: {result.get('title', 'Untitled Blog')[:60]}",
description=f"Complete blog post with {len(result.get('sections', []))} sections",
prompt=f"Title: {result.get('title', 'Untitled')}\nSections: {len(result.get('sections', []))}",
tags=["blog", "complete", "content"],
asset_metadata={
"section_count": len(result.get('sections', [])),
"model": result.get('model'),
},
subdirectory="complete",
file_extension=".md"
)
except Exception as track_error:
logger.warning(f"Failed to track blog content asset: {track_error}")
# If task failed with subscription error, return HTTP error so frontend interceptor can catch it
if status.get('status') == 'failed' and status.get('error_status') in [429, 402]:
error_data = status.get('error_data', {}) or {}
@@ -420,10 +493,40 @@ async def analyze_flow_advanced(request: Dict[str, Any]) -> Dict[str, Any]:
@router.post("/section/optimize", response_model=BlogOptimizeResponse)
async def optimize_section(request: BlogOptimizeRequest) -> BlogOptimizeResponse:
async def optimize_section(
request: BlogOptimizeRequest,
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db)
) -> BlogOptimizeResponse:
"""Optimize a specific section for better quality and engagement."""
try:
return await service.optimize_section(request)
response = await service.optimize_section(request)
# Save and track text content (non-blocking)
if response.optimized:
try:
user_id = str(current_user.get('id', '')) if current_user else None
if user_id:
save_and_track_text_content(
db=db,
user_id=user_id,
content=response.optimized,
source_module="blog_writer",
title=f"Optimized Blog Section",
description=f"Optimized blog section content",
prompt=f"Original Content: {request.content[:200]}\nGoals: {request.goals}",
tags=["blog", "section", "optimized"],
asset_metadata={
"optimization_goals": request.goals,
"word_count": len(response.optimized.split()),
},
subdirectory="sections/optimized",
file_extension=".md"
)
except Exception as track_error:
logger.warning(f"Failed to track optimized blog section asset: {track_error}")
return response
except Exception as e:
logger.error(f"Failed to optimize section: {e}")
raise HTTPException(status_code=500, detail=str(e))
@@ -591,13 +694,49 @@ async def start_medium_generation(
@router.get("/generate/medium/status/{task_id}")
async def medium_generation_status(task_id: str):
async def medium_generation_status(
task_id: str,
current_user: Optional[Dict[str, Any]] = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Poll status for medium blog generation task."""
try:
status = await task_manager.get_task_status(task_id)
if status is None:
raise HTTPException(status_code=404, detail="Task not found")
# Track blog content when task completes (non-blocking)
if status.get('status') == 'completed' and status.get('result'):
try:
result = status.get('result', {})
if result.get('sections') and len(result.get('sections', [])) > 0:
user_id = str(current_user.get('id', '')) if current_user else None
if user_id:
# Combine all sections into full blog content
blog_content = f"# {result.get('title', 'Untitled Blog')}\n\n"
for section in result.get('sections', []):
blog_content += f"\n## {section.get('heading', 'Section')}\n\n{section.get('content', '')}\n\n"
save_and_track_text_content(
db=db,
user_id=user_id,
content=blog_content,
source_module="blog_writer",
title=f"Medium Blog: {result.get('title', 'Untitled Blog')[:60]}",
description=f"Medium-length blog post with {len(result.get('sections', []))} sections",
prompt=f"Title: {result.get('title', 'Untitled')}\nSections: {len(result.get('sections', []))}",
tags=["blog", "medium", "complete"],
asset_metadata={
"section_count": len(result.get('sections', [])),
"model": result.get('model'),
"generation_time_ms": result.get('generation_time_ms'),
},
subdirectory="medium",
file_extension=".md"
)
except Exception as track_error:
logger.warning(f"Failed to track medium blog asset: {track_error}")
# If task failed with subscription error, return HTTP error so frontend interceptor can catch it
if status.get('status') == 'failed' and status.get('error_status') in [429, 402]:
error_data = status.get('error_data', {}) or {}
@@ -677,7 +816,8 @@ async def rewrite_status(task_id: str):
@router.post("/titles/generate-seo")
async def generate_seo_titles(
request: Dict[str, Any],
current_user: Dict[str, Any] = Depends(get_current_user)
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Generate 5 SEO-optimized blog titles using research and outline data."""
try:
@@ -722,6 +862,30 @@ async def generate_seo_titles(
user_id=user_id
)
# Save and track titles (non-blocking)
if titles and len(titles) > 0:
try:
titles_content = "# SEO Blog Titles\n\n" + "\n".join([f"{i+1}. {title}" for i, title in enumerate(titles)])
save_and_track_text_content(
db=db,
user_id=user_id,
content=titles_content,
source_module="blog_writer",
title=f"SEO Blog Titles: {primary_keywords[0] if primary_keywords else 'Blog'}",
description=f"SEO-optimized blog title suggestions",
prompt=f"Primary Keywords: {primary_keywords}\nSearch Intent: {search_intent}\nWord Count: {word_count}",
tags=["blog", "titles", "seo"],
asset_metadata={
"title_count": len(titles),
"primary_keywords": primary_keywords,
"search_intent": search_intent,
},
subdirectory="titles",
file_extension=".md"
)
except Exception as track_error:
logger.warning(f"Failed to track SEO titles asset: {track_error}")
return {
"success": True,
"titles": titles
@@ -736,7 +900,8 @@ async def generate_seo_titles(
@router.post("/introductions/generate")
async def generate_introductions(
request: Dict[str, Any],
current_user: Dict[str, Any] = Depends(get_current_user)
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Generate 3 varied blog introductions using research, outline, and content."""
try:
@@ -781,6 +946,33 @@ async def generate_introductions(
user_id=user_id
)
# Save and track introductions (non-blocking)
if introductions and len(introductions) > 0:
try:
intro_content = f"# Blog Introductions for: {blog_title}\n\n"
for i, intro in enumerate(introductions, 1):
intro_content += f"## Introduction {i}\n\n{intro}\n\n"
save_and_track_text_content(
db=db,
user_id=user_id,
content=intro_content,
source_module="blog_writer",
title=f"Blog Introductions: {blog_title[:60]}",
description=f"Blog introduction variations",
prompt=f"Blog Title: {blog_title}\nPrimary Keywords: {primary_keywords}\nSearch Intent: {search_intent}",
tags=["blog", "introductions"],
asset_metadata={
"introduction_count": len(introductions),
"blog_title": blog_title,
"search_intent": search_intent,
},
subdirectory="introductions",
file_extension=".md"
)
except Exception as track_error:
logger.warning(f"Failed to track blog introductions asset: {track_error}")
return {
"success": True,
"introductions": introductions