Implement V2 AI functions and enhance progress handling
- Added support for new V2 functions: `auto_cluster_v2` and `generate_ideas_v2`, including backend logic and API endpoints. - Updated model configuration to ensure V2 functions validate the presence of models before execution. - Enhanced progress modal to provide better feedback during asynchronous tasks, including task IDs for debugging. - Updated frontend components to integrate new V2 functionalities and improve user experience with clustering and idea generation.
This commit is contained in:
@@ -571,6 +571,118 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
||||
'error': f'Unexpected error: {str(e)}'
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['post'], url_path='auto_cluster_v2', url_name='auto_cluster_v2')
|
||||
def auto_cluster_v2(self, request):
|
||||
"""Auto-cluster keywords V2 - New workflow function with max 50 keywords"""
|
||||
import logging
|
||||
from igny8_core.ai.tasks import run_ai_task
|
||||
from kombu.exceptions import OperationalError as KombuOperationalError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
# Get account
|
||||
account = getattr(request, 'account', None)
|
||||
account_id = account.id if account else None
|
||||
|
||||
# Check model exists - no default, only execute if model is present
|
||||
if account:
|
||||
from igny8_core.modules.system.models import IntegrationSettings
|
||||
openai_settings = IntegrationSettings.objects.filter(
|
||||
integration_type='openai',
|
||||
account=account,
|
||||
is_active=True
|
||||
).first()
|
||||
if not openai_settings or not openai_settings.config or not openai_settings.config.get('model'):
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'AI model not configured. Please configure OpenAI model in Integration settings.'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Prepare payload
|
||||
payload = {
|
||||
'ids': request.data.get('ids', []),
|
||||
'sector_id': request.data.get('sector_id')
|
||||
}
|
||||
|
||||
logger.info(f"auto_cluster_v2 called with ids={payload['ids']}, sector_id={payload.get('sector_id')}")
|
||||
|
||||
# Validate basic input
|
||||
if not payload['ids']:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'No keywords selected'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if len(payload['ids']) > 50:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'Maximum 50 keywords allowed for clustering'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Try to queue Celery task
|
||||
try:
|
||||
if hasattr(run_ai_task, 'delay'):
|
||||
task = run_ai_task.delay(
|
||||
function_name='auto_cluster_v2',
|
||||
payload=payload,
|
||||
account_id=account_id
|
||||
)
|
||||
logger.info(f"Task queued: {task.id}")
|
||||
return Response({
|
||||
'success': True,
|
||||
'task_id': str(task.id),
|
||||
'message': 'Clustering started'
|
||||
}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
# Celery not available - execute synchronously
|
||||
logger.warning("Celery not available, executing synchronously")
|
||||
result = run_ai_task(
|
||||
function_name='auto_cluster_v2',
|
||||
payload=payload,
|
||||
account_id=account_id
|
||||
)
|
||||
if result.get('success'):
|
||||
return Response({
|
||||
'success': True,
|
||||
**result
|
||||
}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': result.get('error', 'Clustering failed')
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
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_v2',
|
||||
payload=payload,
|
||||
account_id=account_id
|
||||
)
|
||||
if result.get('success'):
|
||||
return Response({
|
||||
'success': True,
|
||||
**result
|
||||
}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': result.get('error', 'Clustering failed')
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in auto_cluster_v2: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error in auto_cluster_v2: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': f'Unexpected error: {str(e)}'
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class ClusterViewSet(SiteSectorModelViewSet):
|
||||
"""
|
||||
@@ -810,6 +922,117 @@ class ClusterViewSet(SiteSectorModelViewSet):
|
||||
'success': False,
|
||||
'error': f'Unexpected error: {str(e)}'
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['post'], url_path='generate_ideas_v2', url_name='generate_ideas_v2')
|
||||
def generate_ideas_v2(self, request):
|
||||
"""Generate ideas V2 - Single cluster only"""
|
||||
import logging
|
||||
from igny8_core.ai.tasks import run_ai_task
|
||||
from kombu.exceptions import OperationalError as KombuOperationalError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
# Get account
|
||||
account = getattr(request, 'account', None)
|
||||
account_id = account.id if account else None
|
||||
|
||||
# Check model exists - no default, only execute if model is present
|
||||
if account:
|
||||
from igny8_core.modules.system.models import IntegrationSettings
|
||||
openai_settings = IntegrationSettings.objects.filter(
|
||||
integration_type='openai',
|
||||
account=account,
|
||||
is_active=True
|
||||
).first()
|
||||
if not openai_settings or not openai_settings.config or not openai_settings.config.get('model'):
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'AI model not configured. Please configure OpenAI model in Integration settings.'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Prepare payload
|
||||
payload = {
|
||||
'ids': request.data.get('ids', []),
|
||||
}
|
||||
|
||||
logger.info(f"generate_ideas_v2 called with ids={payload['ids']}")
|
||||
|
||||
# Validate basic input - exactly one cluster
|
||||
if not payload['ids']:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'No cluster selected'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if len(payload['ids']) > 1:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'Only one cluster can be processed at a time'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Try to queue Celery task
|
||||
try:
|
||||
if hasattr(run_ai_task, 'delay'):
|
||||
task = run_ai_task.delay(
|
||||
function_name='generate_ideas_v2',
|
||||
payload=payload,
|
||||
account_id=account_id
|
||||
)
|
||||
logger.info(f"Task queued: {task.id}")
|
||||
return Response({
|
||||
'success': True,
|
||||
'task_id': str(task.id),
|
||||
'message': 'Idea generation started'
|
||||
}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
# Celery not available - execute synchronously
|
||||
logger.warning("Celery not available, executing synchronously")
|
||||
result = run_ai_task(
|
||||
function_name='generate_ideas_v2',
|
||||
payload=payload,
|
||||
account_id=account_id
|
||||
)
|
||||
if result.get('success'):
|
||||
return Response({
|
||||
'success': True,
|
||||
**result
|
||||
}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': result.get('error', 'Idea generation failed')
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
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='generate_ideas_v2',
|
||||
payload=payload,
|
||||
account_id=account_id
|
||||
)
|
||||
if result.get('success'):
|
||||
return Response({
|
||||
'success': True,
|
||||
**result
|
||||
}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': result.get('error', 'Idea generation failed')
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in generate_ideas_v2: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error in generate_ideas_v2: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': f'Unexpected error: {str(e)}'
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user