reaminign phase 1-2 tasks

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-16 22:17:33 +00:00
parent 7f8982a0ab
commit 92f51859fe
2 changed files with 109 additions and 335 deletions

View File

@@ -17,6 +17,9 @@ from igny8_core.api.permissions import IsAuthenticatedAndActive, IsViewerOrAbove
from .models import Keywords, Clusters, ContentIdeas from .models import Keywords, Clusters, ContentIdeas
from .serializers import KeywordSerializer, ContentIdeasSerializer from .serializers import KeywordSerializer, ContentIdeasSerializer
from .cluster_serializers import ClusterSerializer from .cluster_serializers import ClusterSerializer
from igny8_core.business.planning.services.clustering_service import ClusteringService
from igny8_core.business.planning.services.ideas_service import IdeasService
from igny8_core.business.billing.exceptions import InsufficientCreditsError
@extend_schema_view( @extend_schema_view(
@@ -568,93 +571,55 @@ class KeywordViewSet(SiteSectorModelViewSet):
@action(detail=False, methods=['post'], url_path='auto_cluster', url_name='auto_cluster') @action(detail=False, methods=['post'], url_path='auto_cluster', url_name='auto_cluster')
def auto_cluster(self, request): def auto_cluster(self, request):
"""Auto-cluster keywords using AI - New unified framework""" """Auto-cluster keywords using ClusteringService"""
import logging import logging
from igny8_core.ai.tasks import run_ai_task
from kombu.exceptions import OperationalError as KombuOperationalError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
keyword_ids = request.data.get('ids', [])
sector_id = request.data.get('sector_id')
# Get account # Get account
account = getattr(request, 'account', None) account = getattr(request, 'account', None)
account_id = account.id if account else None if not account:
# Prepare payload
payload = {
'ids': request.data.get('ids', []),
'sector_id': request.data.get('sector_id')
}
logger.info(f"auto_cluster called with ids={payload['ids']}, sector_id={payload.get('sector_id')}")
# Validate basic input
if not payload['ids']:
return error_response( return error_response(
error='No IDs provided', error='Account is required',
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
request=request request=request
) )
if len(payload['ids']) > 20: # Use service to cluster keywords
return error_response( service = ClusteringService()
error='Maximum 20 keywords allowed for clustering',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Try to queue Celery task
try: try:
if hasattr(run_ai_task, 'delay'): result = service.cluster_keywords(keyword_ids, account, sector_id)
task = run_ai_task.delay(
function_name='auto_cluster', if result.get('success'):
payload=payload, if 'task_id' in result:
account_id=account_id # Async task queued
) return success_response(
logger.info(f"Task queued: {task.id}") data={'task_id': result['task_id']},
return success_response( message=result.get('message', 'Clustering started'),
data={'task_id': str(task.id)}, request=request
message='Clustering started', )
request=request else:
) # Synchronous execution
else:
# Celery not available - execute synchronously
logger.warning("Celery not available, executing synchronously")
result = run_ai_task(
function_name='auto_cluster',
payload=payload,
account_id=account_id
)
if result.get('success'):
return success_response( return success_response(
data=result, data=result,
request=request request=request
) )
else:
return error_response(
error=result.get('error', 'Clustering failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except (KombuOperationalError, ConnectionError) as e:
# Broker connection failed - fall back to synchronous execution
logger.warning(f"Celery broker unavailable, falling back to synchronous execution: {str(e)}")
result = run_ai_task(
function_name='auto_cluster',
payload=payload,
account_id=account_id
)
if result.get('success'):
return success_response(
data=result,
request=request
)
else: else:
return error_response( return error_response(
error=result.get('error', 'Clustering failed'), error=result.get('error', 'Clustering failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request request=request
) )
except InsufficientCreditsError as e:
return error_response(
error=str(e),
status_code=status.HTTP_402_PAYMENT_REQUIRED,
request=request
)
except Exception as e: except Exception as e:
logger.error(f"Error in auto_cluster: {str(e)}", exc_info=True) logger.error(f"Error in auto_cluster: {str(e)}", exc_info=True)
return error_response( return error_response(
@@ -843,92 +808,54 @@ class ClusterViewSet(SiteSectorModelViewSet):
@action(detail=False, methods=['post'], url_path='auto_generate_ideas', url_name='auto_generate_ideas') @action(detail=False, methods=['post'], url_path='auto_generate_ideas', url_name='auto_generate_ideas')
def auto_generate_ideas(self, request): def auto_generate_ideas(self, request):
"""Auto-generate ideas for clusters using AI - New unified framework""" """Auto-generate ideas for clusters using IdeasService"""
import logging import logging
from igny8_core.ai.tasks import run_ai_task
from kombu.exceptions import OperationalError as KombuOperationalError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
cluster_ids = request.data.get('ids', [])
# Get account # Get account
account = getattr(request, 'account', None) account = getattr(request, 'account', None)
account_id = account.id if account else None if not account:
# Prepare payload
payload = {
'ids': request.data.get('ids', [])
}
logger.info(f"auto_generate_ideas called with ids={payload['ids']}")
# Validate basic input
if not payload['ids']:
return error_response( return error_response(
error='No IDs provided', error='Account is required',
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
request=request request=request
) )
if len(payload['ids']) > 10: # Use service to generate ideas
return error_response( service = IdeasService()
error='Maximum 10 clusters allowed for idea generation',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Try to queue Celery task
try: try:
if hasattr(run_ai_task, 'delay'): result = service.generate_ideas(cluster_ids, account)
task = run_ai_task.delay(
function_name='auto_generate_ideas', if result.get('success'):
payload=payload, if 'task_id' in result:
account_id=account_id # Async task queued
) return success_response(
logger.info(f"Task queued: {task.id}") data={'task_id': result['task_id']},
return success_response( message=result.get('message', 'Idea generation started'),
data={'task_id': str(task.id)}, request=request
message='Idea generation started', )
request=request else:
) # Synchronous execution
else:
# Celery not available - execute synchronously
logger.warning("Celery not available, executing synchronously")
result = run_ai_task(
function_name='auto_generate_ideas',
payload=payload,
account_id=account_id
)
if result.get('success'):
return success_response( return success_response(
data=result, data=result,
request=request request=request
) )
else:
return error_response(
error=result.get('error', 'Idea generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except (KombuOperationalError, ConnectionError) as e:
# Broker connection failed - fall back to synchronous execution
logger.warning(f"Celery broker unavailable, falling back to synchronous execution: {str(e)}")
result = run_ai_task(
function_name='auto_generate_ideas',
payload=payload,
account_id=account_id
)
if result.get('success'):
return success_response(
data=result,
request=request
)
else: else:
return error_response( return error_response(
error=result.get('error', 'Idea generation failed'), error=result.get('error', 'Idea generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request request=request
) )
except InsufficientCreditsError as e:
return error_response(
error=str(e),
status_code=status.HTTP_402_PAYMENT_REQUIRED,
request=request
)
except Exception as e: except Exception as e:
logger.error(f"Error in auto_generate_ideas: {str(e)}", exc_info=True) logger.error(f"Error in auto_generate_ideas: {str(e)}", exc_info=True)
return error_response( return error_response(

View File

@@ -12,6 +12,8 @@ from igny8_core.api.throttles import DebugScopedRateThrottle
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsViewerOrAbove, IsEditorOrAbove from igny8_core.api.permissions import IsAuthenticatedAndActive, IsViewerOrAbove, IsEditorOrAbove
from .models import Tasks, Images, Content from .models import Tasks, Images, Content
from .serializers import TasksSerializer, ImagesSerializer, ContentSerializer from .serializers import TasksSerializer, ImagesSerializer, ContentSerializer
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.business.billing.exceptions import InsufficientCreditsError
@extend_schema_view( @extend_schema_view(
@@ -137,17 +139,14 @@ class TasksViewSet(SiteSectorModelViewSet):
@action(detail=False, methods=['post'], url_path='auto_generate_content', url_name='auto_generate_content') @action(detail=False, methods=['post'], url_path='auto_generate_content', url_name='auto_generate_content')
def auto_generate_content(self, request): def auto_generate_content(self, request):
"""Auto-generate content for tasks using AI""" """Auto-generate content for tasks using ContentGenerationService"""
import logging import logging
from django.db import OperationalError, DatabaseError, IntegrityError
from django.core.exceptions import ValidationError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
ids = request.data.get('ids', []) ids = request.data.get('ids', [])
if not ids: if not ids:
logger.warning("auto_generate_content: No IDs provided")
return error_response( return error_response(
error='No IDs provided', error='No IDs provided',
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
@@ -155,229 +154,77 @@ class TasksViewSet(SiteSectorModelViewSet):
) )
if len(ids) > 10: if len(ids) > 10:
logger.warning(f"auto_generate_content: Too many IDs provided: {len(ids)}")
return error_response( return error_response(
error='Maximum 10 tasks allowed for content generation', error='Maximum 10 tasks allowed for content generation',
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
request=request request=request
) )
logger.info(f"auto_generate_content: Processing {len(ids)} task IDs: {ids}")
# Get account # Get account
account = getattr(request, 'account', None) account = getattr(request, 'account', None)
account_id = account.id if account else None if not account:
logger.info(f"auto_generate_content: Account ID: {account_id}")
# Validate task IDs exist in database before proceeding
try:
queryset = self.get_queryset()
existing_tasks = queryset.filter(id__in=ids)
existing_count = existing_tasks.count()
existing_ids = list(existing_tasks.values_list('id', flat=True))
logger.info(f"auto_generate_content: Found {existing_count} existing tasks out of {len(ids)} requested")
logger.info(f"auto_generate_content: Existing task IDs: {existing_ids}")
if existing_count == 0:
logger.error(f"auto_generate_content: No tasks found for IDs: {ids}")
return error_response(
error=f'No tasks found for the provided IDs: {ids}',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
if existing_count < len(ids):
missing_ids = set(ids) - set(existing_ids)
logger.warning(f"auto_generate_content: Some task IDs not found: {missing_ids}")
# Continue with existing tasks, but log warning
except (OperationalError, DatabaseError) as db_error:
logger.error("=" * 80)
logger.error("DATABASE ERROR: Failed to query tasks")
logger.error(f" - Error type: {type(db_error).__name__}")
logger.error(f" - Error message: {str(db_error)}")
logger.error(f" - Requested IDs: {ids}")
logger.error(f" - Account ID: {account_id}")
logger.error("=" * 80, exc_info=True)
return error_response( return error_response(
error=f'Database error while querying tasks: {str(db_error)}', error='Account is required',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
# Try to queue Celery task, fall back to synchronous if Celery not available
try:
from igny8_core.ai.tasks import run_ai_task
from kombu.exceptions import OperationalError as KombuOperationalError
if hasattr(run_ai_task, 'delay'):
# Celery is available - queue async task
logger.info(f"auto_generate_content: Queuing Celery task for {len(ids)} tasks")
try:
task = run_ai_task.delay(
function_name='generate_content',
payload={'ids': ids},
account_id=account_id
)
logger.info(f"auto_generate_content: Celery task queued successfully: {task.id}")
return success_response(
data={'task_id': str(task.id)},
message='Content generation started',
request=request
)
except KombuOperationalError as celery_error:
logger.error("=" * 80)
logger.error("CELERY ERROR: Failed to queue task")
logger.error(f" - Error type: {type(celery_error).__name__}")
logger.error(f" - Error message: {str(celery_error)}")
logger.error(f" - Task IDs: {ids}")
logger.error(f" - Account ID: {account_id}")
logger.error("=" * 80, exc_info=True)
return error_response(
error='Task queue unavailable. Please try again.',
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
request=request
)
except Exception as celery_error:
logger.error("=" * 80)
logger.error("CELERY ERROR: Failed to queue task")
logger.error(f" - Error type: {type(celery_error).__name__}")
logger.error(f" - Error message: {str(celery_error)}")
logger.error(f" - Task IDs: {ids}")
logger.error(f" - Account ID: {account_id}")
logger.error("=" * 80, exc_info=True)
# Fall back to synchronous execution
logger.info("auto_generate_content: Falling back to synchronous execution")
result = run_ai_task(
function_name='generate_content',
payload={'ids': ids},
account_id=account_id
)
if result.get('success'):
return success_response(
data={'tasks_updated': result.get('count', 0)},
message='Content generated successfully (synchronous)',
request=request
)
else:
return error_response(
error=result.get('error', 'Content generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
else:
# Celery not available - execute synchronously
logger.info(f"auto_generate_content: Executing synchronously (Celery not available)")
result = run_ai_task(
function_name='generate_content',
payload={'ids': ids},
account_id=account_id
)
if result.get('success'):
logger.info(f"auto_generate_content: Synchronous execution successful: {result.get('count', 0)} tasks updated")
return success_response(
data={'tasks_updated': result.get('count', 0)},
message='Content generated successfully',
request=request
)
else:
logger.error(f"auto_generate_content: Synchronous execution failed: {result.get('error', 'Unknown error')}")
return error_response(
error=result.get('error', 'Content generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except ImportError as import_error:
logger.error(f"auto_generate_content: ImportError - tasks module not available: {str(import_error)}")
# Tasks module not available - update status only
try:
queryset = self.get_queryset()
tasks = queryset.filter(id__in=ids, status='queued')
updated_count = tasks.update(status='completed', content='[AI content generation not available]')
logger.info(f"auto_generate_content: Updated {updated_count} tasks (AI generation not available)")
return success_response(
data={'updated_count': updated_count},
message='Tasks updated (AI generation not available)',
request=request
)
except (OperationalError, DatabaseError) as db_error:
logger.error("=" * 80)
logger.error("DATABASE ERROR: Failed to update tasks")
logger.error(f" - Error type: {type(db_error).__name__}")
logger.error(f" - Error message: {str(db_error)}")
logger.error("=" * 80, exc_info=True)
return error_response(
error=f'Database error while updating tasks: {str(db_error)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except (OperationalError, DatabaseError) as db_error:
logger.error("=" * 80)
logger.error("DATABASE ERROR: Failed during task execution")
logger.error(f" - Error type: {type(db_error).__name__}")
logger.error(f" - Error message: {str(db_error)}")
logger.error(f" - Task IDs: {ids}")
logger.error(f" - Account ID: {account_id}")
logger.error("=" * 80, exc_info=True)
return error_response(
error=f'Database error during content generation: {str(db_error)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except IntegrityError as integrity_error:
logger.error("=" * 80)
logger.error("INTEGRITY ERROR: Data integrity violation")
logger.error(f" - Error message: {str(integrity_error)}")
logger.error(f" - Task IDs: {ids}")
logger.error("=" * 80, exc_info=True)
return error_response(
error=f'Data integrity error: {str(integrity_error)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except ValidationError as validation_error:
logger.error(f"auto_generate_content: ValidationError: {str(validation_error)}")
return error_response(
error=f'Validation error: {str(validation_error)}',
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
request=request request=request
) )
except Exception as e: # Validate task IDs exist
logger.error("=" * 80) queryset = self.get_queryset()
logger.error("UNEXPECTED ERROR in auto_generate_content") existing_tasks = queryset.filter(id__in=ids, account=account)
logger.error(f" - Error type: {type(e).__name__}") existing_count = existing_tasks.count()
logger.error(f" - Error message: {str(e)}")
logger.error(f" - Task IDs: {ids}") if existing_count == 0:
logger.error(f" - Account ID: {account_id}")
logger.error("=" * 80, exc_info=True)
return error_response( return error_response(
error=f'Unexpected error: {str(e)}', error=f'No tasks found for the provided IDs: {ids}',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
# Use service to generate content
service = ContentGenerationService()
try:
result = service.generate_content(ids, account)
if result.get('success'):
if 'task_id' in result:
# Async task queued
return success_response(
data={'task_id': result['task_id']},
message=result.get('message', 'Content generation started'),
request=request
)
else:
# Synchronous execution
return success_response(
data=result,
message='Content generated successfully',
request=request
)
else:
return error_response(
error=result.get('error', 'Content generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except InsufficientCreditsError as e:
return error_response(
error=str(e),
status_code=status.HTTP_402_PAYMENT_REQUIRED,
request=request
)
except Exception as e:
logger.error(f"Error in auto_generate_content: {str(e)}", exc_info=True)
return error_response(
error=f'Content generation failed: {str(e)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request request=request
) )
except Exception as outer_error: except Exception as e:
logger.error("=" * 80) logger.error(f"Unexpected error in auto_generate_content: {str(e)}", exc_info=True)
logger.error("CRITICAL ERROR: Outer exception handler")
logger.error(f" - Error type: {type(outer_error).__name__}")
logger.error(f" - Error message: {str(outer_error)}")
logger.error("=" * 80, exc_info=True)
return error_response( return error_response(
error=f'Critical error: {str(outer_error)}', error=f'Unexpected error: {str(e)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request request=request
) )