Add Linker and Optimizer modules with API integration and frontend components
- Added Linker and Optimizer apps to `INSTALLED_APPS` in `settings.py`. - Configured API endpoints for Linker and Optimizer in `urls.py`. - Implemented `OptimizeContentFunction` for content optimization in the AI module. - Created prompts for content optimization and site structure generation. - Updated `OptimizerService` to utilize the new AI function for content optimization. - Developed frontend components including dashboards and content lists for Linker and Optimizer. - Integrated new routes and sidebar navigation for Linker and Optimizer in the frontend. - Enhanced content management with source and sync status filters in the Writer module. - Comprehensive test coverage added for new features and components.
This commit is contained in:
201
backend/igny8_core/modules/optimizer/views.py
Normal file
201
backend/igny8_core/modules/optimizer/views.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import logging
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.viewsets import ViewSet
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
||||
from igny8_core.api.response import success_response, error_response
|
||||
from igny8_core.api.throttles import DebugScopedRateThrottle
|
||||
from igny8_core.business.content.models import Content
|
||||
from igny8_core.business.optimization.services.optimizer_service import OptimizerService
|
||||
from igny8_core.business.billing.exceptions import InsufficientCreditsError
|
||||
from igny8_core.modules.optimizer.serializers import (
|
||||
OptimizeContentSerializer,
|
||||
BatchOptimizeContentSerializer,
|
||||
AnalyzeContentSerializer,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
optimize=extend_schema(tags=['Optimizer']),
|
||||
batch_optimize=extend_schema(tags=['Optimizer']),
|
||||
analyze=extend_schema(tags=['Optimizer']),
|
||||
)
|
||||
class OptimizerViewSet(ViewSet):
|
||||
"""
|
||||
API endpoints for content optimization operations.
|
||||
Unified API Standard v1.0 compliant
|
||||
"""
|
||||
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
|
||||
throttle_scope = 'optimizer'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.optimizer_service = OptimizerService()
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def optimize(self, request):
|
||||
"""
|
||||
Optimize content (auto-detects entry point based on source).
|
||||
|
||||
POST /api/v1/optimizer/optimize/
|
||||
{
|
||||
"content_id": 123,
|
||||
"entry_point": "auto" // optional: auto, writer, wordpress, external, manual
|
||||
}
|
||||
"""
|
||||
serializer = OptimizeContentSerializer(data=request.data, context={'request': request})
|
||||
if not serializer.is_valid():
|
||||
return error_response(serializer.errors, status=status.HTTP_400_BAD_REQUEST, request=request)
|
||||
|
||||
content_id = serializer.validated_data['content_id']
|
||||
entry_point = serializer.validated_data.get('entry_point', 'auto')
|
||||
|
||||
try:
|
||||
content = Content.objects.get(id=content_id)
|
||||
|
||||
# Route to appropriate entry point
|
||||
if entry_point == 'auto':
|
||||
# Auto-detect based on source
|
||||
if content.source == 'igny8':
|
||||
content = self.optimizer_service.optimize_from_writer(content_id)
|
||||
elif content.source == 'wordpress':
|
||||
content = self.optimizer_service.optimize_from_wordpress_sync(content_id)
|
||||
elif content.source in ['shopify', 'custom']:
|
||||
content = self.optimizer_service.optimize_from_external_sync(content_id)
|
||||
else:
|
||||
content = self.optimizer_service.optimize_manual(content_id)
|
||||
elif entry_point == 'writer':
|
||||
content = self.optimizer_service.optimize_from_writer(content_id)
|
||||
elif entry_point == 'wordpress':
|
||||
content = self.optimizer_service.optimize_from_wordpress_sync(content_id)
|
||||
elif entry_point == 'external':
|
||||
content = self.optimizer_service.optimize_from_external_sync(content_id)
|
||||
else: # manual
|
||||
content = self.optimizer_service.optimize_manual(content_id)
|
||||
|
||||
# Get latest optimization task
|
||||
task = content.optimization_tasks.order_by('-created_at').first()
|
||||
|
||||
result = {
|
||||
'content_id': content.id,
|
||||
'optimizer_version': content.optimizer_version,
|
||||
'scores_before': task.scores_before if task else {},
|
||||
'scores_after': content.optimization_scores,
|
||||
'task_id': task.id if task else None,
|
||||
'success': True,
|
||||
}
|
||||
|
||||
return success_response(result, request=request)
|
||||
|
||||
except ValueError as e:
|
||||
return error_response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST, request=request)
|
||||
except InsufficientCreditsError as e:
|
||||
return error_response({'error': str(e)}, status=status.HTTP_402_PAYMENT_REQUIRED, request=request)
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing content {content_id}: {str(e)}", exc_info=True)
|
||||
return error_response({'error': 'Internal server error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, request=request)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def batch_optimize(self, request):
|
||||
"""
|
||||
Batch optimize multiple content items.
|
||||
|
||||
POST /api/v1/optimizer/batch_optimize/
|
||||
{
|
||||
"content_ids": [123, 456, 789],
|
||||
"entry_point": "auto"
|
||||
}
|
||||
"""
|
||||
serializer = BatchOptimizeContentSerializer(data=request.data, context={'request': request})
|
||||
if not serializer.is_valid():
|
||||
return error_response(serializer.errors, status=status.HTTP_400_BAD_REQUEST, request=request)
|
||||
|
||||
content_ids = serializer.validated_data['content_ids']
|
||||
entry_point = serializer.validated_data.get('entry_point', 'auto')
|
||||
|
||||
results = []
|
||||
errors = []
|
||||
|
||||
for content_id in content_ids:
|
||||
try:
|
||||
content = Content.objects.get(id=content_id)
|
||||
|
||||
# Route to appropriate entry point
|
||||
if entry_point == 'auto':
|
||||
if content.source == 'igny8':
|
||||
content = self.optimizer_service.optimize_from_writer(content_id)
|
||||
elif content.source == 'wordpress':
|
||||
content = self.optimizer_service.optimize_from_wordpress_sync(content_id)
|
||||
elif content.source in ['shopify', 'custom']:
|
||||
content = self.optimizer_service.optimize_from_external_sync(content_id)
|
||||
else:
|
||||
content = self.optimizer_service.optimize_manual(content_id)
|
||||
elif entry_point == 'writer':
|
||||
content = self.optimizer_service.optimize_from_writer(content_id)
|
||||
elif entry_point == 'wordpress':
|
||||
content = self.optimizer_service.optimize_from_wordpress_sync(content_id)
|
||||
elif entry_point == 'external':
|
||||
content = self.optimizer_service.optimize_from_external_sync(content_id)
|
||||
else:
|
||||
content = self.optimizer_service.optimize_manual(content_id)
|
||||
|
||||
task = content.optimization_tasks.order_by('-created_at').first()
|
||||
|
||||
results.append({
|
||||
'content_id': content.id,
|
||||
'optimizer_version': content.optimizer_version,
|
||||
'scores_after': content.optimization_scores,
|
||||
'success': True,
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing content {content_id}: {str(e)}", exc_info=True)
|
||||
errors.append({
|
||||
'content_id': content_id,
|
||||
'error': str(e),
|
||||
'success': False,
|
||||
})
|
||||
|
||||
return success_response({
|
||||
'results': results,
|
||||
'errors': errors,
|
||||
'total': len(content_ids),
|
||||
'succeeded': len(results),
|
||||
'failed': len(errors),
|
||||
}, request=request)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def analyze(self, request):
|
||||
"""
|
||||
Analyze content without optimizing (preview scores).
|
||||
|
||||
POST /api/v1/optimizer/analyze/
|
||||
{
|
||||
"content_id": 123
|
||||
}
|
||||
"""
|
||||
serializer = AnalyzeContentSerializer(data=request.data, context={'request': request})
|
||||
if not serializer.is_valid():
|
||||
return error_response(serializer.errors, status=status.HTTP_400_BAD_REQUEST, request=request)
|
||||
|
||||
content_id = serializer.validated_data['content_id']
|
||||
|
||||
try:
|
||||
scores = self.optimizer_service.analyze_only(content_id)
|
||||
|
||||
return success_response({
|
||||
'content_id': content_id,
|
||||
'scores': scores,
|
||||
}, request=request)
|
||||
|
||||
except ValueError as e:
|
||||
return error_response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST, request=request)
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing content {content_id}: {str(e)}", exc_info=True)
|
||||
return error_response({'error': 'Internal server error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, request=request)
|
||||
|
||||
Reference in New Issue
Block a user