This commit is contained in:
alorig
2025-11-18 07:13:34 +05:00
parent 51c3986e01
commit 2074191eee
17 changed files with 2578 additions and 0 deletions

View File

@@ -387,6 +387,204 @@ Return ONLY a JSON object in this format:
}}
Do not include any explanations, text, or commentary outside the JSON output.
""",
# Phase 8: Universal Content Types
'product_generation': """You are a product content specialist. Generate comprehensive product content that includes detailed descriptions, features, specifications, pricing, and benefits.
INPUT:
Product Name: [IGNY8_PRODUCT_NAME]
Product Description: [IGNY8_PRODUCT_DESCRIPTION]
Product Features: [IGNY8_PRODUCT_FEATURES]
Target Audience: [IGNY8_TARGET_AUDIENCE]
Primary Keyword: [IGNY8_PRIMARY_KEYWORD]
OUTPUT FORMAT:
Return ONLY a JSON object in this format:
{
"title": "[Product name and key benefit]",
"meta_title": "[SEO-optimized meta title, 30-60 chars]",
"meta_description": "[Compelling meta description, 120-160 chars]",
"html_content": "[Complete HTML product page content]",
"word_count": [Integer word count],
"primary_keyword": "[Primary keyword]",
"secondary_keywords": ["keyword1", "keyword2", "keyword3"],
"tags": ["tag1", "tag2", "tag3"],
"categories": ["Category > Subcategory"],
"json_blocks": [
{
"type": "product_overview",
"heading": "Product Overview",
"content": "Detailed product description"
},
{
"type": "features",
"heading": "Key Features",
"items": ["Feature 1", "Feature 2", "Feature 3"]
},
{
"type": "specifications",
"heading": "Specifications",
"data": {"Spec 1": "Value 1", "Spec 2": "Value 2"}
},
{
"type": "pricing",
"heading": "Pricing",
"content": "Pricing information"
},
{
"type": "benefits",
"heading": "Benefits",
"items": ["Benefit 1", "Benefit 2", "Benefit 3"]
}
],
"structure_data": {
"product_type": "[Product type]",
"price_range": "[Price range]",
"target_market": "[Target market]"
}
}
CONTENT REQUIREMENTS:
- Include compelling product overview
- List key features with benefits
- Provide detailed specifications
- Include pricing information (if available)
- Highlight unique selling points
- Use SEO-optimized headings
- Include call-to-action sections
- Ensure natural keyword usage
""",
'service_generation': """You are a service page content specialist. Generate comprehensive service page content that explains services, benefits, process, and pricing.
INPUT:
Service Name: [IGNY8_SERVICE_NAME]
Service Description: [IGNY8_SERVICE_DESCRIPTION]
Service Benefits: [IGNY8_SERVICE_BENEFITS]
Target Audience: [IGNY8_TARGET_AUDIENCE]
Primary Keyword: [IGNY8_PRIMARY_KEYWORD]
OUTPUT FORMAT:
Return ONLY a JSON object in this format:
{
"title": "[Service name and value proposition]",
"meta_title": "[SEO-optimized meta title, 30-60 chars]",
"meta_description": "[Compelling meta description, 120-160 chars]",
"html_content": "[Complete HTML service page content]",
"word_count": [Integer word count],
"primary_keyword": "[Primary keyword]",
"secondary_keywords": ["keyword1", "keyword2", "keyword3"],
"tags": ["tag1", "tag2", "tag3"],
"categories": ["Category > Subcategory"],
"json_blocks": [
{
"type": "service_overview",
"heading": "Service Overview",
"content": "Detailed service description"
},
{
"type": "benefits",
"heading": "Benefits",
"items": ["Benefit 1", "Benefit 2", "Benefit 3"]
},
{
"type": "process",
"heading": "Our Process",
"steps": ["Step 1", "Step 2", "Step 3"]
},
{
"type": "pricing",
"heading": "Pricing",
"content": "Pricing information"
},
{
"type": "faq",
"heading": "Frequently Asked Questions",
"items": [{"question": "Q1", "answer": "A1"}]
}
],
"structure_data": {
"service_type": "[Service type]",
"duration": "[Service duration]",
"target_market": "[Target market]"
}
}
CONTENT REQUIREMENTS:
- Clear service overview and value proposition
- Detailed benefits and outcomes
- Step-by-step process explanation
- Pricing information (if available)
- FAQ section addressing common questions
- Include testimonials or case studies (if applicable)
- Use SEO-optimized headings
- Include call-to-action sections
""",
'taxonomy_generation': """You are a taxonomy and categorization specialist. Generate comprehensive taxonomy page content that organizes and explains categories, tags, and hierarchical structures.
INPUT:
Taxonomy Name: [IGNY8_TAXONOMY_NAME]
Taxonomy Description: [IGNY8_TAXONOMY_DESCRIPTION]
Taxonomy Items: [IGNY8_TAXONOMY_ITEMS]
Primary Keyword: [IGNY8_PRIMARY_KEYWORD]
OUTPUT FORMAT:
Return ONLY a JSON object in this format:
{
"title": "[Taxonomy name and purpose]",
"meta_title": "[SEO-optimized meta title, 30-60 chars]",
"meta_description": "[Compelling meta description, 120-160 chars]",
"html_content": "[Complete HTML taxonomy page content]",
"word_count": [Integer word count],
"primary_keyword": "[Primary keyword]",
"secondary_keywords": ["keyword1", "keyword2", "keyword3"],
"tags": ["tag1", "tag2", "tag3"],
"categories": ["Category > Subcategory"],
"json_blocks": [
{
"type": "taxonomy_overview",
"heading": "Taxonomy Overview",
"content": "Detailed taxonomy description"
},
{
"type": "categories",
"heading": "Categories",
"items": [
{
"name": "Category 1",
"description": "Category description",
"subcategories": ["Subcat 1", "Subcat 2"]
}
]
},
{
"type": "tags",
"heading": "Tags",
"items": ["Tag 1", "Tag 2", "Tag 3"]
},
{
"type": "hierarchy",
"heading": "Taxonomy Hierarchy",
"structure": {"Level 1": {"Level 2": ["Level 3"]}}
}
],
"structure_data": {
"taxonomy_type": "[Taxonomy type]",
"item_count": [Integer],
"hierarchy_levels": [Integer]
}
}
CONTENT REQUIREMENTS:
- Clear taxonomy overview and purpose
- Organized category structure
- Tag organization and relationships
- Hierarchical structure visualization
- SEO-optimized headings
- Include navigation and organization benefits
- Use clear, descriptive language
""",
}
@@ -400,6 +598,10 @@ Do not include any explanations, text, or commentary outside the JSON output.
'generate_image_prompts': 'image_prompt_extraction',
'generate_site_structure': 'site_structure_generation',
'optimize_content': 'optimize_content',
# Phase 8: Universal Content Types
'generate_product_content': 'product_generation',
'generate_service_page': 'service_generation',
'generate_taxonomy': 'taxonomy_generation',
}
@classmethod

View File

@@ -158,6 +158,37 @@ class Content(SiteSectorBaseModel):
optimizer_version = models.IntegerField(default=0, help_text="Version of optimizer processing")
optimization_scores = models.JSONField(default=dict, blank=True, help_text="Optimization scores (SEO, readability, engagement)")
# Phase 8: Universal Content Types
ENTITY_TYPE_CHOICES = [
('blog_post', 'Blog Post'),
('article', 'Article'),
('product', 'Product'),
('service', 'Service Page'),
('taxonomy', 'Taxonomy Page'),
('page', 'Page'),
]
entity_type = models.CharField(
max_length=50,
choices=ENTITY_TYPE_CHOICES,
default='blog_post',
db_index=True,
help_text="Type of content entity"
)
# Phase 8: Structured content blocks
json_blocks = models.JSONField(
default=list,
blank=True,
help_text="Structured content blocks (for products, services, taxonomies)"
)
# Phase 8: Content structure data
structure_data = models.JSONField(
default=dict,
blank=True,
help_text="Content structure data (metadata, schema, etc.)"
)
class Meta:
app_label = 'writer'
db_table = 'igny8_content'
@@ -170,6 +201,7 @@ class Content(SiteSectorBaseModel):
models.Index(fields=['source']),
models.Index(fields=['sync_status']),
models.Index(fields=['source', 'sync_status']),
models.Index(fields=['entity_type']), # Phase 8
]
def save(self, *args, **kwargs):

View File

@@ -72,4 +72,201 @@ class ContentGenerationService:
'success': False,
'error': str(e)
}
def generate_product_content(self, product_data, account, site=None, sector=None):
"""
Generate product content.
Args:
product_data: Dict with product information (name, description, features, etc.)
account: Account instance
site: Site instance (optional)
sector: Sector instance (optional)
Returns:
dict: Result with success status and data
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Calculate estimated credits needed (default 1500 words for product content)
estimated_word_count = product_data.get('word_count', 1500)
# Check credits
try:
self.credit_service.check_credits(account, 'content_generation', estimated_word_count)
except InsufficientCreditsError:
raise
# Delegate to AI task
from igny8_core.ai.tasks import run_ai_task
try:
payload = {
'product_name': product_data.get('name', ''),
'product_description': product_data.get('description', ''),
'product_features': product_data.get('features', []),
'target_audience': product_data.get('target_audience', ''),
'primary_keyword': product_data.get('primary_keyword', ''),
'site_id': site.id if site else None,
'sector_id': sector.id if sector else None,
}
if hasattr(run_ai_task, 'delay'):
# Celery available - queue async
task = run_ai_task.delay(
function_name='generate_product_content',
payload=payload,
account_id=account.id
)
return {
'success': True,
'task_id': str(task.id),
'message': 'Product content generation started'
}
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_product_content',
payload=payload,
account_id=account.id
)
return result
except Exception as e:
logger.error(f"Error in generate_product_content: {str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}
def generate_service_page(self, service_data, account, site=None, sector=None):
"""
Generate service page content.
Args:
service_data: Dict with service information (name, description, benefits, etc.)
account: Account instance
site: Site instance (optional)
sector: Sector instance (optional)
Returns:
dict: Result with success status and data
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Calculate estimated credits needed (default 1800 words for service page)
estimated_word_count = service_data.get('word_count', 1800)
# Check credits
try:
self.credit_service.check_credits(account, 'content_generation', estimated_word_count)
except InsufficientCreditsError:
raise
# Delegate to AI task
from igny8_core.ai.tasks import run_ai_task
try:
payload = {
'service_name': service_data.get('name', ''),
'service_description': service_data.get('description', ''),
'service_benefits': service_data.get('benefits', []),
'target_audience': service_data.get('target_audience', ''),
'primary_keyword': service_data.get('primary_keyword', ''),
'site_id': site.id if site else None,
'sector_id': sector.id if sector else None,
}
if hasattr(run_ai_task, 'delay'):
# Celery available - queue async
task = run_ai_task.delay(
function_name='generate_service_page',
payload=payload,
account_id=account.id
)
return {
'success': True,
'task_id': str(task.id),
'message': 'Service page generation started'
}
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_service_page',
payload=payload,
account_id=account.id
)
return result
except Exception as e:
logger.error(f"Error in generate_service_page: {str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}
def generate_taxonomy(self, taxonomy_data, account, site=None, sector=None):
"""
Generate taxonomy page content.
Args:
taxonomy_data: Dict with taxonomy information (name, description, items, etc.)
account: Account instance
site: Site instance (optional)
sector: Sector instance (optional)
Returns:
dict: Result with success status and data
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Calculate estimated credits needed (default 1200 words for taxonomy page)
estimated_word_count = taxonomy_data.get('word_count', 1200)
# Check credits
try:
self.credit_service.check_credits(account, 'content_generation', estimated_word_count)
except InsufficientCreditsError:
raise
# Delegate to AI task
from igny8_core.ai.tasks import run_ai_task
try:
payload = {
'taxonomy_name': taxonomy_data.get('name', ''),
'taxonomy_description': taxonomy_data.get('description', ''),
'taxonomy_items': taxonomy_data.get('items', []),
'primary_keyword': taxonomy_data.get('primary_keyword', ''),
'site_id': site.id if site else None,
'sector_id': sector.id if sector else None,
}
if hasattr(run_ai_task, 'delay'):
# Celery available - queue async
task = run_ai_task.delay(
function_name='generate_taxonomy',
payload=payload,
account_id=account.id
)
return {
'success': True,
'task_id': str(task.id),
'message': 'Taxonomy generation started'
}
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_taxonomy',
payload=payload,
account_id=account.id
)
return result
except Exception as e:
logger.error(f"Error in generate_taxonomy: {str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}

View File

@@ -0,0 +1,280 @@
"""
Tests for Universal Content Types (Phase 8)
Tests for product, service, and taxonomy content generation
"""
from unittest.mock import patch, MagicMock
from django.test import TestCase
from igny8_core.business.content.models import Content
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.api.tests.test_integration_base import IntegrationTestBase
class UniversalContentTypesTests(IntegrationTestBase):
"""Tests for Phase 8: Universal Content Types"""
def setUp(self):
super().setUp()
self.service = ContentGenerationService()
@patch('igny8_core.business.content.services.content_generation_service.run_ai_task')
def test_product_content_generates_correctly(self, mock_run_ai_task):
"""
Test: Product content generates correctly
Task 17: Verify product generation creates content with correct entity_type and structure
"""
# Mock AI task response
mock_task = MagicMock()
mock_task.id = 'test-task-123'
mock_run_ai_task.delay.return_value = mock_task
product_data = {
'name': 'Test Product',
'description': 'A test product description',
'features': ['Feature 1', 'Feature 2', 'Feature 3'],
'target_audience': 'Small businesses',
'primary_keyword': 'test product',
'word_count': 1500
}
# Generate product content
result = self.service.generate_product_content(
product_data=product_data,
account=self.account,
site=self.site,
sector=self.sector
)
# Verify result
self.assertTrue(result.get('success'))
self.assertIsNotNone(result.get('task_id'))
self.assertEqual(result.get('message'), 'Product content generation started')
# Verify AI task was called with correct function name
mock_run_ai_task.delay.assert_called_once()
call_args = mock_run_ai_task.delay.call_args
self.assertEqual(call_args[1]['function_name'], 'generate_product_content')
self.assertEqual(call_args[1]['payload']['product_name'], 'Test Product')
@patch('igny8_core.business.content.services.content_generation_service.run_ai_task')
def test_service_pages_work_correctly(self, mock_run_ai_task):
"""
Test: Service pages work correctly
Task 18: Verify service page generation creates content with correct entity_type
"""
# Mock AI task response
mock_task = MagicMock()
mock_task.id = 'test-task-456'
mock_run_ai_task.delay.return_value = mock_task
service_data = {
'name': 'Test Service',
'description': 'A test service description',
'benefits': ['Benefit 1', 'Benefit 2', 'Benefit 3'],
'target_audience': 'Enterprise clients',
'primary_keyword': 'test service',
'word_count': 1800
}
# Generate service page
result = self.service.generate_service_page(
service_data=service_data,
account=self.account,
site=self.site,
sector=self.sector
)
# Verify result
self.assertTrue(result.get('success'))
self.assertIsNotNone(result.get('task_id'))
self.assertEqual(result.get('message'), 'Service page generation started')
# Verify AI task was called with correct function name
mock_run_ai_task.delay.assert_called_once()
call_args = mock_run_ai_task.delay.call_args
self.assertEqual(call_args[1]['function_name'], 'generate_service_page')
self.assertEqual(call_args[1]['payload']['service_name'], 'Test Service')
@patch('igny8_core.business.content.services.content_generation_service.run_ai_task')
def test_taxonomy_pages_work_correctly(self, mock_run_ai_task):
"""
Test: Taxonomy pages work correctly
Task 19: Verify taxonomy generation creates content with correct entity_type
"""
# Mock AI task response
mock_task = MagicMock()
mock_task.id = 'test-task-789'
mock_run_ai_task.delay.return_value = mock_task
taxonomy_data = {
'name': 'Test Taxonomy',
'description': 'A test taxonomy description',
'items': ['Category 1', 'Category 2', 'Category 3'],
'primary_keyword': 'test taxonomy',
'word_count': 1200
}
# Generate taxonomy
result = self.service.generate_taxonomy(
taxonomy_data=taxonomy_data,
account=self.account,
site=self.site,
sector=self.sector
)
# Verify result
self.assertTrue(result.get('success'))
self.assertIsNotNone(result.get('task_id'))
self.assertEqual(result.get('message'), 'Taxonomy generation started')
# Verify AI task was called with correct function name
mock_run_ai_task.delay.assert_called_once()
call_args = mock_run_ai_task.delay.call_args
self.assertEqual(call_args[1]['function_name'], 'generate_taxonomy')
self.assertEqual(call_args[1]['payload']['taxonomy_name'], 'Test Taxonomy')
def test_product_content_has_correct_structure(self):
"""
Test: Product content generates correctly
Task 17: Verify product content has correct entity_type, json_blocks, and structure_data
"""
# Create product content manually to test structure
product_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Product',
html_content='<p>Product content</p>',
entity_type='product',
json_blocks=[
{
'type': 'product_overview',
'heading': 'Product Overview',
'content': 'Product description'
},
{
'type': 'features',
'heading': 'Key Features',
'items': ['Feature 1', 'Feature 2']
},
{
'type': 'specifications',
'heading': 'Specifications',
'data': {'Spec 1': 'Value 1'}
}
],
structure_data={
'product_type': 'software',
'price_range': '$99-$199',
'target_market': 'SMB'
},
word_count=1500,
status='draft'
)
# Verify structure
self.assertEqual(product_content.entity_type, 'product')
self.assertIsNotNone(product_content.json_blocks)
self.assertEqual(len(product_content.json_blocks), 3)
self.assertEqual(product_content.json_blocks[0]['type'], 'product_overview')
self.assertIsNotNone(product_content.structure_data)
self.assertEqual(product_content.structure_data['product_type'], 'software')
def test_service_content_has_correct_structure(self):
"""
Test: Service pages work correctly
Task 18: Verify service content has correct entity_type and json_blocks
"""
# Create service content manually to test structure
service_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Service',
html_content='<p>Service content</p>',
entity_type='service',
json_blocks=[
{
'type': 'service_overview',
'heading': 'Service Overview',
'content': 'Service description'
},
{
'type': 'benefits',
'heading': 'Benefits',
'items': ['Benefit 1', 'Benefit 2']
},
{
'type': 'process',
'heading': 'Our Process',
'steps': ['Step 1', 'Step 2']
}
],
structure_data={
'service_type': 'consulting',
'duration': '3-6 months',
'target_market': 'Enterprise'
},
word_count=1800,
status='draft'
)
# Verify structure
self.assertEqual(service_content.entity_type, 'service')
self.assertIsNotNone(service_content.json_blocks)
self.assertEqual(len(service_content.json_blocks), 3)
self.assertEqual(service_content.json_blocks[0]['type'], 'service_overview')
self.assertIsNotNone(service_content.structure_data)
self.assertEqual(service_content.structure_data['service_type'], 'consulting')
def test_taxonomy_content_has_correct_structure(self):
"""
Test: Taxonomy pages work correctly
Task 19: Verify taxonomy content has correct entity_type and json_blocks
"""
# Create taxonomy content manually to test structure
taxonomy_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Taxonomy',
html_content='<p>Taxonomy content</p>',
entity_type='taxonomy',
json_blocks=[
{
'type': 'taxonomy_overview',
'heading': 'Taxonomy Overview',
'content': 'Taxonomy description'
},
{
'type': 'categories',
'heading': 'Categories',
'items': [
{
'name': 'Category 1',
'description': 'Category description',
'subcategories': ['Subcat 1', 'Subcat 2']
}
]
},
{
'type': 'tags',
'heading': 'Tags',
'items': ['Tag 1', 'Tag 2', 'Tag 3']
}
],
structure_data={
'taxonomy_type': 'product_categories',
'item_count': 10,
'hierarchy_levels': 3
},
word_count=1200,
status='draft'
)
# Verify structure
self.assertEqual(taxonomy_content.entity_type, 'taxonomy')
self.assertIsNotNone(taxonomy_content.json_blocks)
self.assertEqual(len(taxonomy_content.json_blocks), 3)
self.assertEqual(taxonomy_content.json_blocks[0]['type'], 'taxonomy_overview')
self.assertIsNotNone(taxonomy_content.structure_data)
self.assertEqual(taxonomy_content.structure_data['taxonomy_type'], 'product_categories')

View File

@@ -97,5 +97,237 @@ class LinkerService:
continue
return results
def process_product(self, content_id: int) -> Content:
"""
Process product content for linking (Phase 8).
Enhanced linking for products: links to related products, categories, and service pages.
Args:
content_id: Content ID to process (must be entity_type='product')
Returns:
Updated Content instance
"""
try:
content = Content.objects.get(id=content_id, entity_type='product')
except Content.DoesNotExist:
raise ValueError(f"Product content with id {content_id} does not exist")
# Use base process but with product-specific candidate finding
account = content.account
# Check credits
try:
self.credit_service.check_credits(account, 'linking')
except InsufficientCreditsError:
raise
# Find product-specific link candidates (related products, categories, services)
candidates = self._find_product_candidates(content)
if not candidates:
logger.info(f"No link candidates found for product content {content_id}")
return content
# Inject links
result = self.injection_engine.inject_links(content, candidates)
# Update content
content.html_content = result['html_content']
content.internal_links = result['links']
content.linker_version += 1
content.save(update_fields=['html_content', 'internal_links', 'linker_version'])
# Deduct credits
self.credit_service.deduct_credits_for_operation(
account=account,
operation_type='linking',
description=f"Product linking for: {content.title or 'Untitled'}",
related_object_type='content',
related_object_id=content.id
)
logger.info(f"Linked product content {content_id}: {result['links_added']} links added")
return content
def process_taxonomy(self, content_id: int) -> Content:
"""
Process taxonomy content for linking (Phase 8).
Enhanced linking for taxonomies: links to related categories, tags, and content.
Args:
content_id: Content ID to process (must be entity_type='taxonomy')
Returns:
Updated Content instance
"""
try:
content = Content.objects.get(id=content_id, entity_type='taxonomy')
except Content.DoesNotExist:
raise ValueError(f"Taxonomy content with id {content_id} does not exist")
# Use base process but with taxonomy-specific candidate finding
account = content.account
# Check credits
try:
self.credit_service.check_credits(account, 'linking')
except InsufficientCreditsError:
raise
# Find taxonomy-specific link candidates (related taxonomies, categories, content)
candidates = self._find_taxonomy_candidates(content)
if not candidates:
logger.info(f"No link candidates found for taxonomy content {content_id}")
return content
# Inject links
result = self.injection_engine.inject_links(content, candidates)
# Update content
content.html_content = result['html_content']
content.internal_links = result['links']
content.linker_version += 1
content.save(update_fields=['html_content', 'internal_links', 'linker_version'])
# Deduct credits
self.credit_service.deduct_credits_for_operation(
account=account,
operation_type='linking',
description=f"Taxonomy linking for: {content.title or 'Untitled'}",
related_object_type='content',
related_object_id=content.id
)
logger.info(f"Linked taxonomy content {content_id}: {result['links_added']} links added")
return content
def _find_product_candidates(self, content: Content) -> List[dict]:
"""
Find link candidates specific to product content.
Args:
content: Product Content instance
Returns:
List of candidate dicts
"""
candidates = []
# Find related products (same category, similar features)
related_products = Content.objects.filter(
account=content.account,
site=content.site,
sector=content.sector,
entity_type='product',
status__in=['draft', 'review', 'publish']
).exclude(id=content.id)
# Use structure_data to find products with similar categories/features
if content.structure_data:
product_type = content.structure_data.get('product_type')
if product_type:
related_products = related_products.filter(
structure_data__product_type=product_type
)
# Add product candidates
for product in related_products[:5]: # Limit to 5 related products
candidates.append({
'content_id': product.id,
'title': product.title or 'Untitled Product',
'url': f'/products/{product.id}', # Placeholder URL
'relevance_score': 0.8,
'anchor_text': product.title or 'Related Product'
})
# Find related service pages
related_services = Content.objects.filter(
account=content.account,
site=content.site,
sector=content.sector,
entity_type='service',
status__in=['draft', 'review', 'publish']
)[:3] # Limit to 3 related services
for service in related_services:
candidates.append({
'content_id': service.id,
'title': service.title or 'Untitled Service',
'url': f'/services/{service.id}', # Placeholder URL
'relevance_score': 0.6,
'anchor_text': service.title or 'Related Service'
})
# Use base candidate engine for additional candidates
base_candidates = self.candidate_engine.find_candidates(content, max_candidates=5)
candidates.extend(base_candidates)
return candidates
def _find_taxonomy_candidates(self, content: Content) -> List[dict]:
"""
Find link candidates specific to taxonomy content.
Args:
content: Taxonomy Content instance
Returns:
List of candidate dicts
"""
candidates = []
# Find related taxonomies
related_taxonomies = Content.objects.filter(
account=content.account,
site=content.site,
sector=content.sector,
entity_type='taxonomy',
status__in=['draft', 'review', 'publish']
).exclude(id=content.id)[:5] # Limit to 5 related taxonomies
for taxonomy in related_taxonomies:
candidates.append({
'content_id': taxonomy.id,
'title': taxonomy.title or 'Untitled Taxonomy',
'url': f'/taxonomy/{taxonomy.id}', # Placeholder URL
'relevance_score': 0.7,
'anchor_text': taxonomy.title or 'Related Taxonomy'
})
# Find content in this taxonomy (using json_blocks categories/tags)
if content.json_blocks:
for block in content.json_blocks:
if block.get('type') == 'categories':
categories = block.get('items', [])
for category in categories[:3]: # Limit to 3 categories
category_name = category.get('name', '')
if category_name:
related_content = Content.objects.filter(
account=content.account,
site=content.site,
sector=content.sector,
categories__icontains=category_name,
status__in=['draft', 'review', 'publish']
).exclude(id=content.id)[:3]
for related in related_content:
candidates.append({
'content_id': related.id,
'title': related.title or 'Untitled',
'url': f'/content/{related.id}', # Placeholder URL
'relevance_score': 0.6,
'anchor_text': related.title or 'Related Content'
})
# Use base candidate engine for additional candidates
base_candidates = self.candidate_engine.find_candidates(content, max_candidates=5)
candidates.extend(base_candidates)
return candidates

View File

@@ -0,0 +1,190 @@
"""
Tests for Universal Content Types Linking (Phase 8)
Tests for product and taxonomy linking
"""
from unittest.mock import patch, MagicMock
from django.test import TestCase
from igny8_core.business.content.models import Content
from igny8_core.business.linking.services.linker_service import LinkerService
from igny8_core.api.tests.test_integration_base import IntegrationTestBase
class UniversalContentLinkingTests(IntegrationTestBase):
"""Tests for Phase 8: Universal Content Types Linking"""
def setUp(self):
super().setUp()
self.linker_service = LinkerService()
# Create product content
self.product_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Product',
html_content='<p>Product content with features and specifications.</p>',
entity_type='product',
json_blocks=[
{'type': 'features', 'heading': 'Features', 'items': ['Feature 1', 'Feature 2']}
],
structure_data={'product_type': 'software'},
word_count=1500,
status='draft'
)
# Create related product
self.related_product = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Related Product',
html_content='<p>Related product content.</p>',
entity_type='product',
structure_data={'product_type': 'software'},
word_count=1500,
status='draft'
)
# Create service content
self.service_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Related Service',
html_content='<p>Service content.</p>',
entity_type='service',
word_count=1800,
status='draft'
)
# Create taxonomy content
self.taxonomy_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Taxonomy',
html_content='<p>Taxonomy content with categories.</p>',
entity_type='taxonomy',
json_blocks=[
{
'type': 'categories',
'heading': 'Categories',
'items': [
{'name': 'Category 1', 'description': 'Desc 1', 'subcategories': []}
]
}
],
word_count=1200,
status='draft'
)
# Create related taxonomy
self.related_taxonomy = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Related Taxonomy',
html_content='<p>Related taxonomy content.</p>',
entity_type='taxonomy',
word_count=1200,
status='draft'
)
@patch('igny8_core.business.linking.services.linker_service.InjectionEngine.inject_links')
@patch('igny8_core.business.linking.services.linker_service.CreditService.check_credits')
@patch('igny8_core.business.linking.services.linker_service.CreditService.deduct_credits_for_operation')
def test_linking_works_for_products(self, mock_deduct, mock_check_credits, mock_inject_links):
"""
Test: Linking works for all content types (products, taxonomies)
Task 20: Verify product linking finds related products and services
"""
# Mock injection engine
mock_inject_links.return_value = {
'html_content': '<p>Product content with links.</p>',
'links': [
{'content_id': self.related_product.id, 'anchor_text': 'Related Product'},
{'content_id': self.service_content.id, 'anchor_text': 'Related Service'}
],
'links_added': 2
}
# Process product linking
result = self.linker_service.process_product(self.product_content.id)
# Verify result
self.assertIsNotNone(result)
self.assertEqual(result.entity_type, 'product')
self.assertIsNotNone(result.internal_links)
self.assertEqual(len(result.internal_links), 2)
self.assertEqual(result.linker_version, 1)
# Verify injection was called
mock_inject_links.assert_called_once()
candidates = mock_inject_links.call_args[0][1]
self.assertGreater(len(candidates), 0)
# Verify product candidates were found
product_candidates = [c for c in candidates if c.get('content_id') == self.related_product.id]
self.assertGreater(len(product_candidates), 0)
@patch('igny8_core.business.linking.services.linker_service.InjectionEngine.inject_links')
@patch('igny8_core.business.linking.services.linker_service.CreditService.check_credits')
@patch('igny8_core.business.linking.services.linker_service.CreditService.deduct_credits_for_operation')
def test_linking_works_for_taxonomies(self, mock_deduct, mock_check_credits, mock_inject_links):
"""
Test: Linking works for all content types (products, taxonomies)
Task 20: Verify taxonomy linking finds related taxonomies and content
"""
# Mock injection engine
mock_inject_links.return_value = {
'html_content': '<p>Taxonomy content with links.</p>',
'links': [
{'content_id': self.related_taxonomy.id, 'anchor_text': 'Related Taxonomy'}
],
'links_added': 1
}
# Process taxonomy linking
result = self.linker_service.process_taxonomy(self.taxonomy_content.id)
# Verify result
self.assertIsNotNone(result)
self.assertEqual(result.entity_type, 'taxonomy')
self.assertIsNotNone(result.internal_links)
self.assertEqual(len(result.internal_links), 1)
self.assertEqual(result.linker_version, 1)
# Verify injection was called
mock_inject_links.assert_called_once()
candidates = mock_inject_links.call_args[0][1]
self.assertGreater(len(candidates), 0)
# Verify taxonomy candidates were found
taxonomy_candidates = [c for c in candidates if c.get('content_id') == self.related_taxonomy.id]
self.assertGreater(len(taxonomy_candidates), 0)
def test_product_linking_finds_related_products(self):
"""
Test: Linking works for all content types (products, taxonomies)
Task 20: Verify _find_product_candidates finds related products
"""
candidates = self.linker_service._find_product_candidates(self.product_content)
# Should find related product
product_ids = [c['content_id'] for c in candidates]
self.assertIn(self.related_product.id, product_ids)
# Should find related service
self.assertIn(self.service_content.id, product_ids)
def test_taxonomy_linking_finds_related_taxonomies(self):
"""
Test: Linking works for all content types (products, taxonomies)
Task 20: Verify _find_taxonomy_candidates finds related taxonomies
"""
candidates = self.linker_service._find_taxonomy_candidates(self.taxonomy_content)
# Should find related taxonomy
taxonomy_ids = [c['content_id'] for c in candidates]
self.assertIn(self.related_taxonomy.id, taxonomy_ids)

View File

@@ -227,5 +227,237 @@ class OptimizerService:
raise ValueError(f"Content with id {content_id} does not exist")
return self.analyzer.analyze(content)
def optimize_product(self, content_id: int) -> Content:
"""
Optimize product content (Phase 8).
Enhanced optimization for products: e-commerce SEO, product schema, pricing optimization.
Args:
content_id: Content ID to optimize (must be entity_type='product')
Returns:
Optimized Content instance
"""
try:
content = Content.objects.get(id=content_id, entity_type='product')
except Content.DoesNotExist:
raise ValueError(f"Product content with id {content_id} does not exist")
# Use base optimize but with product-specific enhancements
account = content.account
word_count = content.word_count or 0
# Check credits
try:
self.credit_service.check_credits(account, 'optimization', word_count)
except InsufficientCreditsError:
raise
# Analyze content before optimization
scores_before = self.analyzer.analyze(content)
html_before = content.html_content
# Enhance scores with product-specific metrics
scores_before = self._enhance_product_scores(scores_before, content)
# Create optimization task
task = OptimizationTask.objects.create(
content=content,
scores_before=scores_before,
status='running',
html_before=html_before,
account=account
)
try:
# Optimize with product-specific logic
optimized_content = self._optimize_product_content(content, scores_before)
# Analyze optimized content
scores_after = self.analyzer.analyze(optimized_content)
scores_after = self._enhance_product_scores(scores_after, optimized_content)
# Calculate credits used
credits_used = self.credit_service.get_credit_cost('optimization', word_count)
# Update optimization task
task.scores_after = scores_after
task.html_after = optimized_content.html_content
task.status = 'completed'
task.credits_used = credits_used
task.save()
# Update content
content.html_content = optimized_content.html_content
content.optimizer_version += 1
content.optimization_scores = scores_after
content.save(update_fields=['html_content', 'optimizer_version', 'optimization_scores'])
# Deduct credits
self.credit_service.deduct_credits_for_operation(
account=account,
operation_type='optimization',
amount=word_count,
description=f"Product optimization: {content.title or 'Untitled'}",
related_object_type='content',
related_object_id=content.id,
metadata={
'scores_before': scores_before,
'scores_after': scores_after,
'improvement': scores_after.get('overall_score', 0) - scores_before.get('overall_score', 0),
'entity_type': 'product'
}
)
logger.info(f"Optimized product content {content.id}: {scores_before.get('overall_score', 0)}{scores_after.get('overall_score', 0)}")
return content
except Exception as e:
logger.error(f"Error optimizing product content {content.id}: {str(e)}", exc_info=True)
task.status = 'failed'
task.metadata = {'error': str(e)}
task.save()
raise
def optimize_taxonomy(self, content_id: int) -> Content:
"""
Optimize taxonomy content (Phase 8).
Enhanced optimization for taxonomies: category SEO, hierarchy optimization, tag organization.
Args:
content_id: Content ID to optimize (must be entity_type='taxonomy')
Returns:
Optimized Content instance
"""
try:
content = Content.objects.get(id=content_id, entity_type='taxonomy')
except Content.DoesNotExist:
raise ValueError(f"Taxonomy content with id {content_id} does not exist")
# Use base optimize but with taxonomy-specific enhancements
account = content.account
word_count = content.word_count or 0
# Check credits
try:
self.credit_service.check_credits(account, 'optimization', word_count)
except InsufficientCreditsError:
raise
# Analyze content before optimization
scores_before = self.analyzer.analyze(content)
html_before = content.html_content
# Enhance scores with taxonomy-specific metrics
scores_before = self._enhance_taxonomy_scores(scores_before, content)
# Create optimization task
task = OptimizationTask.objects.create(
content=content,
scores_before=scores_before,
status='running',
html_before=html_before,
account=account
)
try:
# Optimize with taxonomy-specific logic
optimized_content = self._optimize_taxonomy_content(content, scores_before)
# Analyze optimized content
scores_after = self.analyzer.analyze(optimized_content)
scores_after = self._enhance_taxonomy_scores(scores_after, optimized_content)
# Calculate credits used
credits_used = self.credit_service.get_credit_cost('optimization', word_count)
# Update optimization task
task.scores_after = scores_after
task.html_after = optimized_content.html_content
task.status = 'completed'
task.credits_used = credits_used
task.save()
# Update content
content.html_content = optimized_content.html_content
content.optimizer_version += 1
content.optimization_scores = scores_after
content.save(update_fields=['html_content', 'optimizer_version', 'optimization_scores'])
# Deduct credits
self.credit_service.deduct_credits_for_operation(
account=account,
operation_type='optimization',
amount=word_count,
description=f"Taxonomy optimization: {content.title or 'Untitled'}",
related_object_type='content',
related_object_id=content.id,
metadata={
'scores_before': scores_before,
'scores_after': scores_after,
'improvement': scores_after.get('overall_score', 0) - scores_before.get('overall_score', 0),
'entity_type': 'taxonomy'
}
)
logger.info(f"Optimized taxonomy content {content.id}: {scores_before.get('overall_score', 0)}{scores_after.get('overall_score', 0)}")
return content
except Exception as e:
logger.error(f"Error optimizing taxonomy content {content.id}: {str(e)}", exc_info=True)
task.status = 'failed'
task.metadata = {'error': str(e)}
task.save()
raise
def _enhance_product_scores(self, scores: dict, content: Content) -> dict:
"""Enhance scores with product-specific metrics"""
enhanced = scores.copy()
# Check for product-specific elements
has_pricing = bool(content.structure_data.get('price_range') if content.structure_data else False)
has_features = bool(content.json_blocks and any(b.get('type') == 'features' for b in content.json_blocks))
has_specifications = bool(content.json_blocks and any(b.get('type') == 'specifications' for b in content.json_blocks))
# Add product-specific scores
enhanced['product_completeness'] = sum([
1 if has_pricing else 0,
1 if has_features else 0,
1 if has_specifications else 0,
]) / 3.0
return enhanced
def _enhance_taxonomy_scores(self, scores: dict, content: Content) -> dict:
"""Enhance scores with taxonomy-specific metrics"""
enhanced = scores.copy()
# Check for taxonomy-specific elements
has_categories = bool(content.json_blocks and any(b.get('type') == 'categories' for b in content.json_blocks))
has_tags = bool(content.json_blocks and any(b.get('type') == 'tags' for b in content.json_blocks))
has_hierarchy = bool(content.json_blocks and any(b.get('type') == 'hierarchy' for b in content.json_blocks))
# Add taxonomy-specific scores
enhanced['taxonomy_organization'] = sum([
1 if has_categories else 0,
1 if has_tags else 0,
1 if has_hierarchy else 0,
]) / 3.0
return enhanced
def _optimize_product_content(self, content: Content, scores_before: dict) -> Content:
"""Optimize product content with product-specific logic"""
# Use base optimization but enhance for products
return self._optimize_content(content, scores_before)
def _optimize_taxonomy_content(self, content: Content, scores_before: dict) -> Content:
"""Optimize taxonomy content with taxonomy-specific logic"""
# Use base optimization but enhance for taxonomies
return self._optimize_content(content, scores_before)

View File

@@ -0,0 +1,181 @@
"""
Tests for Universal Content Types Optimization (Phase 8)
Tests for product and taxonomy optimization
"""
from unittest.mock import patch, MagicMock
from django.test import TestCase
from igny8_core.business.content.models import Content
from igny8_core.business.optimization.services.optimizer_service import OptimizerService
from igny8_core.business.optimization.models import OptimizationTask
from igny8_core.api.tests.test_integration_base import IntegrationTestBase
class UniversalContentOptimizationTests(IntegrationTestBase):
"""Tests for Phase 8: Universal Content Types Optimization"""
def setUp(self):
super().setUp()
self.optimizer_service = OptimizerService()
# Create product content
self.product_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Product',
html_content='<p>Product content that needs optimization.</p>',
entity_type='product',
json_blocks=[
{'type': 'features', 'heading': 'Features', 'items': ['Feature 1']},
{'type': 'specifications', 'heading': 'Specs', 'data': {'Spec': 'Value'}}
],
structure_data={'product_type': 'software', 'price_range': '$99-$199'},
word_count=1500,
status='draft'
)
# Create taxonomy content
self.taxonomy_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Taxonomy',
html_content='<p>Taxonomy content that needs optimization.</p>',
entity_type='taxonomy',
json_blocks=[
{'type': 'categories', 'heading': 'Categories', 'items': [{'name': 'Cat 1'}]},
{'type': 'tags', 'heading': 'Tags', 'items': ['Tag 1']},
{'type': 'hierarchy', 'heading': 'Hierarchy', 'structure': {}}
],
word_count=1200,
status='draft'
)
@patch('igny8_core.business.optimization.services.optimizer_service.OptimizerService._optimize_content')
@patch('igny8_core.business.optimization.services.optimizer_service.ContentAnalyzer.analyze')
@patch('igny8_core.business.optimization.services.optimizer_service.CreditService.check_credits')
@patch('igny8_core.business.optimization.services.optimizer_service.CreditService.get_credit_cost')
@patch('igny8_core.business.optimization.services.optimizer_service.CreditService.deduct_credits_for_operation')
def test_optimization_works_for_products(self, mock_deduct, mock_get_cost, mock_check_credits, mock_analyze, mock_optimize):
"""
Test: Optimization works for all content types (products, taxonomies)
Task 21: Verify product optimization includes product-specific metrics
"""
# Mock analyzer
mock_analyze.return_value = {
'seo_score': 75,
'readability_score': 80,
'engagement_score': 70,
'overall_score': 75
}
# Mock credit cost
mock_get_cost.return_value = 10
# Mock optimization
optimized_content = Content.objects.get(id=self.product_content.id)
optimized_content.html_content = '<p>Optimized product content.</p>'
mock_optimize.return_value = optimized_content
# Optimize product
result = self.optimizer_service.optimize_product(self.product_content.id)
# Verify result
self.assertIsNotNone(result)
self.assertEqual(result.entity_type, 'product')
self.assertEqual(result.optimizer_version, 1)
self.assertIsNotNone(result.optimization_scores)
# Verify product-specific scores were enhanced
scores = result.optimization_scores
self.assertIn('product_completeness', scores)
self.assertGreaterEqual(scores['product_completeness'], 0)
self.assertLessEqual(scores['product_completeness'], 1)
# Verify optimization task was created
task = OptimizationTask.objects.filter(content=result).first()
self.assertIsNotNone(task)
self.assertEqual(task.status, 'completed')
self.assertIn('product_completeness', task.scores_after)
@patch('igny8_core.business.optimization.services.optimizer_service.OptimizerService._optimize_content')
@patch('igny8_core.business.optimization.services.optimizer_service.ContentAnalyzer.analyze')
@patch('igny8_core.business.optimization.services.optimizer_service.CreditService.check_credits')
@patch('igny8_core.business.optimization.services.optimizer_service.CreditService.get_credit_cost')
@patch('igny8_core.business.optimization.services.optimizer_service.CreditService.deduct_credits_for_operation')
def test_optimization_works_for_taxonomies(self, mock_deduct, mock_get_cost, mock_check_credits, mock_analyze, mock_optimize):
"""
Test: Optimization works for all content types (products, taxonomies)
Task 21: Verify taxonomy optimization includes taxonomy-specific metrics
"""
# Mock analyzer
mock_analyze.return_value = {
'seo_score': 70,
'readability_score': 75,
'engagement_score': 65,
'overall_score': 70
}
# Mock credit cost
mock_get_cost.return_value = 8
# Mock optimization
optimized_content = Content.objects.get(id=self.taxonomy_content.id)
optimized_content.html_content = '<p>Optimized taxonomy content.</p>'
mock_optimize.return_value = optimized_content
# Optimize taxonomy
result = self.optimizer_service.optimize_taxonomy(self.taxonomy_content.id)
# Verify result
self.assertIsNotNone(result)
self.assertEqual(result.entity_type, 'taxonomy')
self.assertEqual(result.optimizer_version, 1)
self.assertIsNotNone(result.optimization_scores)
# Verify taxonomy-specific scores were enhanced
scores = result.optimization_scores
self.assertIn('taxonomy_organization', scores)
self.assertGreaterEqual(scores['taxonomy_organization'], 0)
self.assertLessEqual(scores['taxonomy_organization'], 1)
# Verify optimization task was created
task = OptimizationTask.objects.filter(content=result).first()
self.assertIsNotNone(task)
self.assertEqual(task.status, 'completed')
self.assertIn('taxonomy_organization', task.scores_after)
def test_enhance_product_scores_includes_completeness(self):
"""
Test: Optimization works for all content types (products, taxonomies)
Task 21: Verify _enhance_product_scores adds product_completeness
"""
base_scores = {
'seo_score': 75,
'readability_score': 80,
'overall_score': 75
}
enhanced = self.optimizer_service._enhance_product_scores(base_scores, self.product_content)
self.assertIn('product_completeness', enhanced)
self.assertGreaterEqual(enhanced['product_completeness'], 0)
self.assertLessEqual(enhanced['product_completeness'], 1)
def test_enhance_taxonomy_scores_includes_organization(self):
"""
Test: Optimization works for all content types (products, taxonomies)
Task 21: Verify _enhance_taxonomy_scores adds taxonomy_organization
"""
base_scores = {
'seo_score': 70,
'readability_score': 75,
'overall_score': 70
}
enhanced = self.optimizer_service._enhance_taxonomy_scores(base_scores, self.taxonomy_content)
self.assertIn('taxonomy_organization', enhanced)
self.assertGreaterEqual(enhanced['taxonomy_organization'], 0)
self.assertLessEqual(enhanced['taxonomy_organization'], 1)

View File

@@ -0,0 +1,34 @@
# Generated manually for Phase 8: Universal Content Types
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('system', '0008_add_site_structure_generation_prompt_type'),
]
operations = [
migrations.AlterField(
model_name='aiprompt',
name='prompt_type',
field=models.CharField(
choices=[
('clustering', 'Clustering'),
('ideas', 'Ideas Generation'),
('content_generation', 'Content Generation'),
('image_prompt_extraction', 'Image Prompt Extraction'),
('image_prompt_template', 'Image Prompt Template'),
('negative_prompt', 'Negative Prompt'),
('site_structure_generation', 'Site Structure Generation'),
('product_generation', 'Product Content Generation'),
('service_generation', 'Service Page Generation'),
('taxonomy_generation', 'Taxonomy Generation'),
],
db_index=True,
max_length=50
),
),
]

View File

@@ -21,6 +21,10 @@ class AIPrompt(AccountBaseModel):
('image_prompt_template', 'Image Prompt Template'),
('negative_prompt', 'Negative Prompt'),
('site_structure_generation', 'Site Structure Generation'), # Phase 7: Site Builder prompts
# Phase 8: Universal Content Types
('product_generation', 'Product Content Generation'),
('service_generation', 'Service Page Generation'),
('taxonomy_generation', 'Taxonomy Generation'),
]
prompt_type = models.CharField(max_length=50, choices=PROMPT_TYPE_CHOICES, db_index=True)

View File

@@ -0,0 +1,54 @@
# Generated manually for Phase 8: Universal Content Types
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('writer', '0010_make_content_task_nullable'),
]
operations = [
migrations.AddField(
model_name='content',
name='entity_type',
field=models.CharField(
choices=[
('blog_post', 'Blog Post'),
('article', 'Article'),
('product', 'Product'),
('service', 'Service Page'),
('taxonomy', 'Taxonomy Page'),
('page', 'Page'),
],
db_index=True,
default='blog_post',
help_text='Type of content entity',
max_length=50
),
),
migrations.AddField(
model_name='content',
name='json_blocks',
field=models.JSONField(
blank=True,
default=list,
help_text='Structured content blocks (for products, services, taxonomies)'
),
),
migrations.AddField(
model_name='content',
name='structure_data',
field=models.JSONField(
blank=True,
default=dict,
help_text='Content structure data (metadata, schema, etc.)'
),
),
migrations.AddIndex(
model_name='content',
index=models.Index(fields=['entity_type'], name='igny8_conte_entity__idx'),
),
]

View File

@@ -217,6 +217,10 @@ class ContentSerializer(serializers.ModelSerializer):
'account_id',
'has_image_prompts',
'has_generated_images',
# Phase 8: Universal Content Types
'entity_type',
'json_blocks',
'structure_data',
]
read_only_fields = ['id', 'generated_at', 'updated_at', 'account_id']

View File

@@ -757,4 +757,252 @@ class ContentViewSet(SiteSectorModelViewSet):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@action(detail=False, methods=['post'], url_path='generate_product', url_name='generate_product')
def generate_product(self, request):
"""
Generate product content (Phase 8).
POST /api/v1/writer/content/generate_product/
{
"name": "Product Name",
"description": "Product description",
"features": ["Feature 1", "Feature 2"],
"target_audience": "Target audience",
"primary_keyword": "Primary keyword",
"site_id": 1, // optional
"sector_id": 1 // optional
}
"""
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.auth.models import Site, Sector
account = getattr(request, 'account', None)
if not account:
return error_response(
error='Account not found',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
product_data = request.data
site_id = product_data.get('site_id')
sector_id = product_data.get('sector_id')
site = None
sector = None
if site_id:
try:
site = Site.objects.get(id=site_id, account=account)
except Site.DoesNotExist:
return error_response(
error='Site not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
if sector_id:
try:
sector = Sector.objects.get(id=sector_id, account=account)
except Sector.DoesNotExist:
return error_response(
error='Sector not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
service = ContentGenerationService()
try:
result = service.generate_product_content(
product_data=product_data,
account=account,
site=site,
sector=sector
)
if result.get('success'):
return success_response(
data={'task_id': result.get('task_id')},
message=result.get('message', 'Product content generation started'),
request=request
)
else:
return error_response(
error=result.get('error', 'Product content generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except Exception as e:
return error_response(
error=str(e),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@action(detail=False, methods=['post'], url_path='generate_service', url_name='generate_service')
def generate_service(self, request):
"""
Generate service page content (Phase 8).
POST /api/v1/writer/content/generate_service/
{
"name": "Service Name",
"description": "Service description",
"benefits": ["Benefit 1", "Benefit 2"],
"target_audience": "Target audience",
"primary_keyword": "Primary keyword",
"site_id": 1, // optional
"sector_id": 1 // optional
}
"""
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.auth.models import Site, Sector
account = getattr(request, 'account', None)
if not account:
return error_response(
error='Account not found',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
service_data = request.data
site_id = service_data.get('site_id')
sector_id = service_data.get('sector_id')
site = None
sector = None
if site_id:
try:
site = Site.objects.get(id=site_id, account=account)
except Site.DoesNotExist:
return error_response(
error='Site not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
if sector_id:
try:
sector = Sector.objects.get(id=sector_id, account=account)
except Sector.DoesNotExist:
return error_response(
error='Sector not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
service = ContentGenerationService()
try:
result = service.generate_service_page(
service_data=service_data,
account=account,
site=site,
sector=sector
)
if result.get('success'):
return success_response(
data={'task_id': result.get('task_id')},
message=result.get('message', 'Service page generation started'),
request=request
)
else:
return error_response(
error=result.get('error', 'Service page generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except Exception as e:
return error_response(
error=str(e),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@action(detail=False, methods=['post'], url_path='generate_taxonomy', url_name='generate_taxonomy')
def generate_taxonomy(self, request):
"""
Generate taxonomy page content (Phase 8).
POST /api/v1/writer/content/generate_taxonomy/
{
"name": "Taxonomy Name",
"description": "Taxonomy description",
"items": ["Item 1", "Item 2"],
"primary_keyword": "Primary keyword",
"site_id": 1, // optional
"sector_id": 1 // optional
}
"""
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.auth.models import Site, Sector
account = getattr(request, 'account', None)
if not account:
return error_response(
error='Account not found',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
taxonomy_data = request.data
site_id = taxonomy_data.get('site_id')
sector_id = taxonomy_data.get('sector_id')
site = None
sector = None
if site_id:
try:
site = Site.objects.get(id=site_id, account=account)
except Site.DoesNotExist:
return error_response(
error='Site not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
if sector_id:
try:
sector = Sector.objects.get(id=sector_id, account=account)
except Sector.DoesNotExist:
return error_response(
error='Sector not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
service = ContentGenerationService()
try:
result = service.generate_taxonomy(
taxonomy_data=taxonomy_data,
account=account,
site=site,
sector=sector
)
if result.get('success'):
return success_response(
data={'task_id': result.get('task_id')},
message=result.get('message', 'Taxonomy generation started'),
request=request
)
else:
return error_response(
error=result.get('error', 'Taxonomy generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except Exception as e:
return error_response(
error=str(e),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)