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,64 +571,39 @@ 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
)
logger.info(f"Task queued: {task.id}")
return success_response( return success_response(
data={'task_id': str(task.id)}, data={'task_id': result['task_id']},
message='Clustering started', message=result.get('message', 'Clustering started'),
request=request request=request
) )
else: else:
# Celery not available - execute synchronously # Synchronous execution
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
@@ -636,23 +614,10 @@ class KeywordViewSet(SiteSectorModelViewSet):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request request=request
) )
except (KombuOperationalError, ConnectionError) as e: except InsufficientCreditsError 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:
return error_response( return error_response(
error=result.get('error', 'Clustering failed'), error=str(e),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_402_PAYMENT_REQUIRED,
request=request request=request
) )
except Exception as e: except Exception as e:
@@ -843,63 +808,38 @@ 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
)
logger.info(f"Task queued: {task.id}")
return success_response( return success_response(
data={'task_id': str(task.id)}, data={'task_id': result['task_id']},
message='Idea generation started', message=result.get('message', 'Idea generation started'),
request=request request=request
) )
else: else:
# Celery not available - execute synchronously # Synchronous execution
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
@@ -910,23 +850,10 @@ class ClusterViewSet(SiteSectorModelViewSet):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request request=request
) )
except (KombuOperationalError, ConnectionError) as e: except InsufficientCreditsError 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:
return error_response( return error_response(
error=result.get('error', 'Idea generation failed'), error=str(e),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_402_PAYMENT_REQUIRED,
request=request request=request
) )
except Exception as e: except Exception as e:

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,233 +154,81 @@ 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}") return error_response(
error='Account is required',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Validate task IDs exist in database before proceeding # Validate task IDs exist
try:
queryset = self.get_queryset() queryset = self.get_queryset()
existing_tasks = queryset.filter(id__in=ids) existing_tasks = queryset.filter(id__in=ids, account=account)
existing_count = existing_tasks.count() 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: if existing_count == 0:
logger.error(f"auto_generate_content: No tasks found for IDs: {ids}")
return error_response( return error_response(
error=f'No tasks found for the provided IDs: {ids}', error=f'No tasks found for the provided IDs: {ids}',
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
request=request request=request
) )
if existing_count < len(ids): # Use service to generate content
missing_ids = set(ids) - set(existing_ids) service = ContentGenerationService()
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(
error=f'Database error while querying tasks: {str(db_error)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
# Try to queue Celery task, fall back to synchronous if Celery not available
try: try:
from igny8_core.ai.tasks import run_ai_task result = service.generate_content(ids, account)
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'): if result.get('success'):
if 'task_id' in result:
# Async task queued
return success_response( return success_response(
data={'tasks_updated': result.get('count', 0)}, data={'task_id': result['task_id']},
message='Content generated successfully (synchronous)', message=result.get('message', 'Content generation started'),
request=request request=request
) )
else: else:
return error_response( # Synchronous execution
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( return success_response(
data={'tasks_updated': result.get('count', 0)}, data=result,
message='Content generated successfully', message='Content generated successfully',
request=request request=request
) )
else: else:
logger.error(f"auto_generate_content: Synchronous execution failed: {result.get('error', 'Unknown error')}")
return error_response( return error_response(
error=result.get('error', 'Content generation failed'), error=result.get('error', 'Content 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:
except ImportError as import_error: return error_response(
logger.error(f"auto_generate_content: ImportError - tasks module not available: {str(import_error)}") error=str(e),
# Tasks module not available - update status only status_code=status.HTTP_402_PAYMENT_REQUIRED,
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 request=request
) )
except (OperationalError, DatabaseError) as db_error: except Exception as e:
logger.error("=" * 80) logger.error(f"Error in auto_generate_content: {str(e)}", exc_info=True)
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( return error_response(
error=f'Database error while updating tasks: {str(db_error)}', 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 (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,
request=request
)
except Exception as e: except Exception as e:
logger.error("=" * 80) logger.error(f"Unexpected error in auto_generate_content: {str(e)}", exc_info=True)
logger.error("UNEXPECTED ERROR in auto_generate_content")
logger.error(f" - Error type: {type(e).__name__}")
logger.error(f" - Error message: {str(e)}")
logger.error(f" - Task IDs: {ids}")
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'Unexpected error: {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:
logger.error("=" * 80)
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(
error=f'Critical error: {str(outer_error)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@extend_schema_view( @extend_schema_view(
list=extend_schema(tags=['Writer']), list=extend_schema(tags=['Writer']),