8 Phases refactor

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-03 16:08:02 +00:00
parent 30bbcb08a1
commit 39df00e5ae
55 changed files with 2120 additions and 5527 deletions

View File

@@ -36,8 +36,6 @@ class AIEngine:
return f"{count} task{'s' if count != 1 else ''}"
elif function_name == 'generate_site_structure':
return "1 site blueprint"
elif function_name == 'generate_page_content':
return f"{count} page{'s' if count != 1 else ''}"
return f"{count} item{'s' if count != 1 else ''}"
def _build_validation_message(self, function_name: str, payload: dict, count: int, input_description: str) -> str:
@@ -91,8 +89,6 @@ class AIEngine:
if blueprint and getattr(blueprint, 'name', None):
blueprint_name = f'"{blueprint.name}"'
return f"Preparing site blueprint {blueprint_name}".strip()
elif function_name == 'generate_page_content':
return f"Preparing {count} page{'s' if count != 1 else ''} for content generation"
return f"Preparing {count} item{'s' if count != 1 else ''}"
def _get_ai_call_message(self, function_name: str, count: int) -> str:
@@ -107,8 +103,6 @@ class AIEngine:
return f"Creating image{'s' if count != 1 else ''} with AI"
elif function_name == 'generate_site_structure':
return "Designing complete site architecture"
elif function_name == 'generate_page_content':
return f"Generating structured page content"
return f"Processing with AI"
def _get_parse_message(self, function_name: str) -> str:
@@ -123,8 +117,6 @@ class AIEngine:
return "Processing images"
elif function_name == 'generate_site_structure':
return "Compiling site map"
elif function_name == 'generate_page_content':
return "Structuring content blocks"
return "Processing results"
def _get_parse_message_with_count(self, function_name: str, count: int) -> str:
@@ -145,8 +137,6 @@ class AIEngine:
return "Writing Inarticle Image Prompts"
elif function_name == 'generate_site_structure':
return f"{count} page blueprint{'s' if count != 1 else ''} mapped"
elif function_name == 'generate_page_content':
return f"{count} page{'s' if count != 1 else ''} with structured blocks"
return f"{count} item{'s' if count != 1 else ''} processed"
def _get_save_message(self, function_name: str, count: int) -> str:
@@ -164,8 +154,6 @@ class AIEngine:
return f"Assigning {count} Prompts to Dedicated Slots"
elif function_name == 'generate_site_structure':
return f"Publishing {count} page blueprint{'s' if count != 1 else ''}"
elif function_name == 'generate_page_content':
return f"Saving {count} page{'s' if count != 1 else ''} with content blocks"
return f"Saving {count} item{'s' if count != 1 else ''}"
def execute(self, fn: BaseAIFunction, payload: dict) -> dict:
@@ -524,16 +512,14 @@ class AIEngine:
'generate_image_prompts': 'image_prompt_extraction',
'generate_images': 'image_generation',
'generate_site_structure': 'site_structure_generation',
'generate_page_content': 'content_generation', # Site Builder page content
}
return mapping.get(function_name, function_name)
def _get_estimated_amount(self, function_name, data, payload):
"""Get estimated amount for credit calculation (before operation)"""
if function_name == 'generate_content' or function_name == 'generate_page_content':
if function_name == 'generate_content':
# Estimate word count - tasks don't have word_count field, use default
# For generate_content, data is a list of Task objects
# For generate_page_content, data is a PageBlueprint object
# data is a list of Task objects
if isinstance(data, list) and len(data) > 0:
# Multiple tasks - estimate 1000 words per task
return len(data) * 1000
@@ -554,7 +540,7 @@ class AIEngine:
def _get_actual_amount(self, function_name, save_result, parsed, data):
"""Get actual amount for credit calculation (after operation)"""
if function_name == 'generate_content' or function_name == 'generate_page_content':
if function_name == 'generate_content':
# Get actual word count from saved content
if isinstance(save_result, dict):
word_count = save_result.get('word_count')

View File

@@ -6,7 +6,6 @@ from igny8_core.ai.functions.generate_ideas import GenerateIdeasFunction
from igny8_core.ai.functions.generate_content import GenerateContentFunction
from igny8_core.ai.functions.generate_images import GenerateImagesFunction, generate_images_core
from igny8_core.ai.functions.generate_image_prompts import GenerateImagePromptsFunction
from igny8_core.ai.functions.generate_page_content import GeneratePageContentFunction
__all__ = [
'AutoClusterFunction',
@@ -15,5 +14,4 @@ __all__ = [
'GenerateImagesFunction',
'generate_images_core',
'GenerateImagePromptsFunction',
'GeneratePageContentFunction',
]

View File

@@ -249,7 +249,7 @@ class AutoClusterFunction(BaseAIFunction):
sector=sector,
defaults={
'description': cluster_data.get('description', ''),
'status': 'active',
'status': 'new', # FIXED: Changed from 'active' to 'new'
}
)
else:
@@ -260,7 +260,7 @@ class AutoClusterFunction(BaseAIFunction):
sector__isnull=True,
defaults={
'description': cluster_data.get('description', ''),
'status': 'active',
'status': 'new', # FIXED: Changed from 'active' to 'new'
'sector': None,
}
)
@@ -292,9 +292,10 @@ class AutoClusterFunction(BaseAIFunction):
else:
keyword_filter = keyword_filter.filter(sector__isnull=True)
# FIXED: Ensure keywords status updates from 'new' to 'mapped'
updated_count = keyword_filter.update(
cluster=cluster,
status='mapped'
status='mapped' # Status changes from 'new' to 'mapped'
)
keywords_updated += updated_count

View File

@@ -1,273 +0,0 @@
"""
Generate Page Content AI Function
Site Builder specific content generation that outputs structured JSON blocks.
This is separate from the default writer module's GenerateContentFunction.
It uses different prompts optimized for site builder pages and outputs
structured blocks_json format instead of HTML.
"""
import logging
import json
from typing import Dict, List, Any
from django.db import transaction
from igny8_core.ai.base import BaseAIFunction
from igny8_core.business.site_building.models import PageBlueprint
from igny8_core.business.content.models import Tasks, Content
from igny8_core.ai.ai_core import AICore
from igny8_core.ai.prompts import PromptRegistry
from igny8_core.ai.settings import get_model_config
logger = logging.getLogger(__name__)
class GeneratePageContentFunction(BaseAIFunction):
"""
Generate structured page content for Site Builder pages.
Outputs JSON blocks format optimized for site rendering.
"""
def get_name(self) -> str:
return 'generate_page_content'
def get_metadata(self) -> Dict:
return {
'display_name': 'Generate Page Content',
'description': 'Generate structured page content with JSON blocks for Site Builder',
'phases': {
'INIT': 'Initializing page content generation...',
'PREP': 'Loading page blueprint and building prompt...',
'AI_CALL': 'Generating structured content with AI...',
'PARSE': 'Parsing JSON blocks...',
'SAVE': 'Saving blocks to page...',
'DONE': 'Page content generated!'
}
}
def get_max_items(self) -> int:
return 20 # Max pages per batch
def validate(self, payload: dict, account=None) -> Dict:
"""Validate page blueprint IDs"""
result = super().validate(payload, account)
if not result['valid']:
return result
page_ids = payload.get('ids', [])
if page_ids:
from igny8_core.business.site_building.models import PageBlueprint
queryset = PageBlueprint.objects.filter(id__in=page_ids)
if account:
queryset = queryset.filter(account=account)
if queryset.count() == 0:
return {'valid': False, 'error': 'No page blueprints found'}
return {'valid': True}
def prepare(self, payload: dict, account=None) -> List:
"""Load page blueprints with relationships"""
page_ids = payload.get('ids', [])
queryset = PageBlueprint.objects.filter(id__in=page_ids)
if account:
queryset = queryset.filter(account=account)
# Preload relationships
pages = list(queryset.select_related(
'site_blueprint', 'account', 'site', 'sector'
))
if not pages:
raise ValueError("No page blueprints found")
return pages
def build_prompt(self, data: Any, account=None) -> str:
"""Build page content generation prompt optimized for Site Builder"""
if isinstance(data, list):
page = data[0] if data else None
else:
page = data
if not page:
raise ValueError("No page blueprint provided")
account = account or page.account
# Build page context
page_context = {
'PAGE_TITLE': page.title or page.slug.replace('-', ' ').title(),
'PAGE_SLUG': page.slug,
'PAGE_TYPE': page.type or 'custom',
'SITE_NAME': page.site_blueprint.name if page.site_blueprint else '',
'SITE_DESCRIPTION': page.site_blueprint.description or '',
}
# Extract existing block structure hints
block_hints = []
if page.blocks_json:
for block in page.blocks_json[:5]: # First 5 blocks as hints
if isinstance(block, dict):
block_type = block.get('type', '')
heading = block.get('heading') or block.get('title') or ''
if block_type and heading:
block_hints.append(f"- {block_type}: {heading}")
if block_hints:
page_context['EXISTING_BLOCKS'] = '\n'.join(block_hints)
else:
page_context['EXISTING_BLOCKS'] = 'None (new page)'
# Get site blueprint structure hints
structure_hints = ''
if page.site_blueprint and page.site_blueprint.structure_json:
structure = page.site_blueprint.structure_json
if isinstance(structure, dict):
layout = structure.get('layout', 'default')
theme = structure.get('theme', {})
structure_hints = f"Layout: {layout}\nTheme: {json.dumps(theme, indent=2)}"
page_context['STRUCTURE_HINTS'] = structure_hints or 'Default layout'
# Get prompt from registry (site-builder specific)
prompt = PromptRegistry.get_prompt(
function_name='generate_page_content',
account=account,
context=page_context
)
return prompt
def parse_response(self, response: str, step_tracker=None) -> Dict:
"""Parse AI response - must be JSON with blocks structure"""
import json
# Try to extract JSON from response
try:
# Try direct JSON parse
parsed = json.loads(response.strip())
except json.JSONDecodeError:
# Try to extract JSON object from text
try:
# Look for JSON object in response
start = response.find('{')
end = response.rfind('}')
if start != -1 and end != -1 and end > start:
json_str = response[start:end + 1]
parsed = json.loads(json_str)
else:
raise ValueError("No JSON object found in response")
except (json.JSONDecodeError, ValueError) as e:
logger.error(f"Failed to parse page content response as JSON: {e}")
logger.error(f"Response preview: {response[:500]}")
raise ValueError(f"Invalid JSON response from AI: {str(e)}")
if not isinstance(parsed, dict):
raise ValueError("Response must be a JSON object")
# Validate required fields
if 'blocks' not in parsed and 'blocks_json' not in parsed:
raise ValueError("Response must include 'blocks' or 'blocks_json' field")
# Normalize to 'blocks' key
if 'blocks_json' in parsed:
parsed['blocks'] = parsed.pop('blocks_json')
return parsed
def save_output(
self,
parsed: Any,
original_data: Any,
account=None,
progress_tracker=None,
step_tracker=None
) -> Dict:
"""Save blocks to PageBlueprint and create/update Content record"""
if isinstance(original_data, list):
page = original_data[0] if original_data else None
else:
page = original_data
if not page:
raise ValueError("No page blueprint provided for saving")
if not isinstance(parsed, dict):
raise ValueError("Parsed response must be a dict")
blocks = parsed.get('blocks', [])
if not blocks:
raise ValueError("No blocks found in parsed response")
# Ensure blocks is a list
if not isinstance(blocks, list):
blocks = [blocks]
with transaction.atomic():
# Update PageBlueprint with generated blocks
page.blocks_json = blocks
page.status = 'ready' # Mark as ready after content generation
page.save(update_fields=['blocks_json', 'status', 'updated_at'])
# Find or create associated Task
task_title = f"[Site Builder] {page.title or page.slug.replace('-', ' ').title()}"
task = Tasks.objects.filter(
account=page.account,
site=page.site,
sector=page.sector,
title=task_title
).first()
# Create or update Content record with blocks
if task:
content_record, created = Content.objects.get_or_create(
task=task,
defaults={
'account': page.account,
'site': page.site,
'sector': page.sector,
'title': parsed.get('title') or page.title,
'html_content': parsed.get('html_content', ''),
'word_count': parsed.get('word_count', 0),
'status': 'draft',
'json_blocks': blocks, # Store blocks in json_blocks
'metadata': {
'page_id': page.id,
'page_slug': page.slug,
'page_type': page.type,
'generated_by': 'generate_page_content'
}
}
)
if not created:
# Update existing content
content_record.json_blocks = blocks
content_record.html_content = parsed.get('html_content', content_record.html_content)
content_record.word_count = parsed.get('word_count', content_record.word_count)
content_record.title = parsed.get('title') or content_record.title or page.title
if not content_record.metadata:
content_record.metadata = {}
content_record.metadata.update({
'page_id': page.id,
'page_slug': page.slug,
'page_type': page.type,
'generated_by': 'generate_page_content'
})
content_record.save()
else:
logger.warning(f"No task found for page {page.id}, skipping Content record creation")
content_record = None
logger.info(
f"[GeneratePageContentFunction] Saved {len(blocks)} blocks to page {page.id} "
f"(Content ID: {content_record.id if content_record else 'N/A'})"
)
return {
'count': 1,
'pages_updated': 1,
'blocks_count': len(blocks),
'content_id': content_record.id if content_record else None
}

View File

@@ -526,169 +526,6 @@ CONTENT REQUIREMENTS:
- Include call-to-action sections
""",
'generate_page_content': """You are a Site Builder content specialist. Generate structured page content optimized for website pages with JSON blocks format.
Your task is to generate content that will be rendered as a modern website page with structured blocks (hero, features, testimonials, text, CTA, etc.).
INPUT DATA:
----------
Page Title: [IGNY8_PAGE_TITLE]
Page Slug: [IGNY8_PAGE_SLUG]
Page Type: [IGNY8_PAGE_TYPE] (home, products, blog, contact, about, services, custom)
Site Name: [IGNY8_SITE_NAME]
Site Description: [IGNY8_SITE_DESCRIPTION]
Existing Block Hints: [IGNY8_EXISTING_BLOCKS]
Structure Hints: [IGNY8_STRUCTURE_HINTS]
OUTPUT FORMAT:
--------------
Return ONLY a JSON object in this exact structure:
{{
"title": "[Page title - SEO optimized, natural]",
"html_content": "[Full HTML content for fallback/SEO - complete article]",
"word_count": [Integer - word count of HTML content],
"blocks": [
{{
"type": "hero",
"data": {{
"heading": "[Compelling hero headline]",
"subheading": "[Supporting subheadline]",
"content": "[Brief hero description - 1-2 sentences]",
"buttonText": "[CTA button text]",
"buttonLink": "[CTA link URL]"
}}
}},
{{
"type": "text",
"data": {{
"heading": "[Section heading]",
"content": "[Rich text content with paragraphs, lists, etc.]"
}}
}},
{{
"type": "features",
"data": {{
"heading": "[Features section heading]",
"content": [
"[Feature 1: Description]",
"[Feature 2: Description]",
"[Feature 3: Description]"
]
}}
}},
{{
"type": "testimonials",
"data": {{
"heading": "[Testimonials heading]",
"subheading": "[Optional subheading]",
"content": [
"[Testimonial quote 1]",
"[Testimonial quote 2]",
"[Testimonial quote 3]"
]
}}
}},
{{
"type": "cta",
"data": {{
"heading": "[CTA heading]",
"subheading": "[CTA subheading]",
"content": "[CTA description]",
"buttonText": "[Button text]",
"buttonLink": "[Button link]"
}}
}}
]
}}
BLOCK TYPE GUIDELINES:
----------------------
Based on page type, use appropriate blocks:
**Home Page:**
- Start with "hero" block (compelling headline + CTA)
- Follow with "features" or "text" blocks
- Include "testimonials" block
- End with "cta" block
**Products Page:**
- Start with "text" block (product overview)
- Use "features" or "grid" blocks for product listings
- Include "text" blocks for product details
**Blog Page:**
- Use "text" blocks for article content
- Can include "quote" blocks for highlights
- Structure as readable article format
**Contact Page:**
- Start with "text" block (contact info)
- Use "form" block structure hints
- Include "text" blocks for location/hours
**About Page:**
- Start with "hero" or "text" block
- Use "features" for team/values
- Include "stats" block if applicable
- End with "text" block
**Services Page:**
- Start with "text" block (service overview)
- Use "features" for service offerings
- Include "text" blocks for details
CONTENT REQUIREMENTS:
---------------------
1. **Hero Block** (for home/about pages):
- Compelling headline (8-12 words)
- Clear value proposition
- Strong CTA button
2. **Text Blocks**:
- Natural, engaging copy
- SEO-optimized headings
- Varied content (paragraphs, lists, emphasis)
3. **Features Blocks**:
- 3-6 features
- Clear benefit statements
- Action-oriented language
4. **Testimonials Blocks**:
- 3-5 authentic-sounding testimonials
- Specific, believable quotes
- Varied lengths
5. **CTA Blocks**:
- Clear value proposition
- Strong action words
- Compelling button text
6. **HTML Content** (for SEO):
- Complete article version of all blocks
- Proper HTML structure
- SEO-optimized with headings, paragraphs, lists
- 800-1500 words total
TONE & STYLE:
-------------
- Professional but approachable
- Clear and concise
- Benefit-focused
- Action-oriented
- Natural keyword usage (not forced)
- No generic phrases or placeholder text
IMPORTANT:
----------
- Return ONLY the JSON object
- Do NOT include markdown formatting
- Do NOT include explanations or comments
- Ensure all blocks have proper "type" and "data" structure
- HTML content should be complete and standalone
- Blocks should be optimized for the specific page type""",
'taxonomy_generation': """You are a taxonomy and categorization specialist. Generate comprehensive taxonomy page content that organizes and explains categories, tags, and hierarchical structures.
INPUT:
@@ -764,7 +601,6 @@ CONTENT REQUIREMENTS:
'extract_image_prompts': 'image_prompt_extraction',
'generate_image_prompts': 'image_prompt_extraction',
'generate_site_structure': 'site_structure_generation',
'generate_page_content': 'generate_page_content', # Site Builder specific
'optimize_content': 'optimize_content',
# Phase 8: Universal Content Types
'generate_product_content': 'product_generation',