From aa8b8a975600f4bd1c07fec4cb8317f70559e902 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Wed, 3 Dec 2025 10:29:13 +0000 Subject: [PATCH] more fixes --- backend/igny8_core/admin/site.py | 2 - backend/igny8_core/ai/functions/__init__.py | 2 - .../ai/functions/generate_site_structure.py | 214 ------------------ backend/igny8_core/ai/registry.py | 6 - .../automation/migrations/0001_initial.py | 14 +- .../automation/services/automation_service.py | 6 +- .../igny8_core/business/automation/views.py | 6 +- .../tests/test_deployment_service.py | 15 +- .../business/site_building/models.py | 32 ++- .../business/site_building/tests/base.py | 22 +- .../tests/test_bulk_generation.py | 6 +- backend/igny8_core/urls.py | 6 + .../src/components/Automation/ActivityLog.tsx | 3 - .../src/components/Automation/RunHistory.tsx | 3 - 14 files changed, 61 insertions(+), 276 deletions(-) delete mode 100644 backend/igny8_core/ai/functions/generate_site_structure.py diff --git a/backend/igny8_core/admin/site.py b/backend/igny8_core/admin/site.py index 62e3f60f..7916e23a 100644 --- a/backend/igny8_core/admin/site.py +++ b/backend/igny8_core/admin/site.py @@ -45,8 +45,6 @@ class Igny8AdminSite(admin.AdminSite): ('igny8_core_auth', 'User'), ('igny8_core_auth', 'SiteUserAccess'), ('igny8_core_auth', 'PasswordResetToken'), - ('site_building', 'SiteBlueprint'), - ('site_building', 'PageBlueprint'), ], }, 'Global Reference Data': { diff --git a/backend/igny8_core/ai/functions/__init__.py b/backend/igny8_core/ai/functions/__init__.py index 5c2ba99d..aa5bfa00 100644 --- a/backend/igny8_core/ai/functions/__init__.py +++ b/backend/igny8_core/ai/functions/__init__.py @@ -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_site_structure import GenerateSiteStructureFunction from igny8_core.ai.functions.generate_page_content import GeneratePageContentFunction __all__ = [ @@ -16,6 +15,5 @@ __all__ = [ 'GenerateImagesFunction', 'generate_images_core', 'GenerateImagePromptsFunction', - 'GenerateSiteStructureFunction', 'GeneratePageContentFunction', ] diff --git a/backend/igny8_core/ai/functions/generate_site_structure.py b/backend/igny8_core/ai/functions/generate_site_structure.py deleted file mode 100644 index 91898696..00000000 --- a/backend/igny8_core/ai/functions/generate_site_structure.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -Generate Site Structure AI Function -Phase 3 – Site Builder -""" -import json -import logging -from typing import Any, Dict, List, Tuple - -from django.utils.text import slugify - -from igny8_core.ai.base import BaseAIFunction -from igny8_core.ai.prompts import PromptRegistry -from igny8_core.business.site_building.models import SiteBlueprint, PageBlueprint - -logger = logging.getLogger(__name__) - - -class GenerateSiteStructureFunction(BaseAIFunction): - """AI function that turns a business brief into a full site blueprint.""" - - def get_name(self) -> str: - return 'generate_site_structure' - - def get_metadata(self) -> Dict: - metadata = super().get_metadata() - metadata.update({ - 'display_name': 'Generate Site Structure', - 'description': 'Create site/page architecture from business brief, objectives, and style guides.', - 'phases': { - 'INIT': 'Validating blueprint data…', - 'PREP': 'Preparing site context…', - 'AI_CALL': 'Generating site structure with AI…', - 'PARSE': 'Parsing generated blueprint…', - 'SAVE': 'Saving pages and blocks…', - 'DONE': 'Site structure ready!' - } - }) - return metadata - - def validate(self, payload: dict, account=None) -> Dict[str, Any]: - if not payload.get('ids'): - return {'valid': False, 'error': 'Site blueprint ID is required'} - return {'valid': True} - - def prepare(self, payload: dict, account=None) -> Dict[str, Any]: - blueprint_ids = payload.get('ids', []) - queryset = SiteBlueprint.objects.filter(id__in=blueprint_ids) - if account: - queryset = queryset.filter(account=account) - blueprint = queryset.select_related('account', 'site').prefetch_related('pages').first() - if not blueprint: - raise ValueError("Site blueprint not found") - - config = blueprint.config_json or {} - business_brief = payload.get('business_brief') or config.get('business_brief') or '' - objectives = payload.get('objectives') or config.get('objectives') or [] - style = payload.get('style') or config.get('style') or {} - - return { - 'blueprint': blueprint, - 'business_brief': business_brief, - 'objectives': objectives, - 'style': style, - } - - def build_prompt(self, data: Dict[str, Any], account=None) -> str: - blueprint: SiteBlueprint = data['blueprint'] - objectives = data.get('objectives') or [] - objectives_text = '\n'.join(f"- {obj}" for obj in objectives) if isinstance(objectives, list) else objectives - style = data.get('style') or {} - style_text = json.dumps(style, indent=2) if isinstance(style, dict) and style else str(style) - - existing_pages = [ - { - 'title': page.title, - 'slug': page.slug, - 'type': page.type, - 'status': page.status, - } - for page in blueprint.pages.all() - ] - - context = { - 'BUSINESS_BRIEF': data.get('business_brief', ''), - 'OBJECTIVES': objectives_text or 'Create a full marketing site with clear navigation.', - 'STYLE': style_text or 'Modern, responsive, accessible web design.', - 'SITE_INFO': json.dumps({ - 'site_name': blueprint.name, - 'site_description': blueprint.description, - 'hosting_type': blueprint.hosting_type, - 'existing_pages': existing_pages, - 'existing_structure': blueprint.structure_json or {}, - }, indent=2) - } - - return PromptRegistry.get_prompt( - 'generate_site_structure', - account=account or blueprint.account, - context=context - ) - - def parse_response(self, response: str, step_tracker=None) -> Dict[str, Any]: - if not response: - raise ValueError("AI response is empty") - - response = response.strip() - try: - return self._ensure_dict(json.loads(response)) - except json.JSONDecodeError: - logger.warning("Response not valid JSON, attempting to extract JSON object") - cleaned = self._extract_json_object(response) - if cleaned: - return self._ensure_dict(json.loads(cleaned)) - raise ValueError("Unable to parse AI response into JSON") - - def save_output( - self, - parsed: Dict[str, Any], - original_data: Dict[str, Any], - account=None, - progress_tracker=None, - step_tracker=None - ) -> Dict[str, Any]: - blueprint: SiteBlueprint = original_data['blueprint'] - structure = self._ensure_dict(parsed) - pages = structure.get('pages', []) - - blueprint.structure_json = structure - blueprint.status = 'ready' - blueprint.save(update_fields=['structure_json', 'status', 'updated_at']) - - created, updated, deleted = self._sync_page_blueprints(blueprint, pages) - - message = f"Pages synced (created: {created}, updated: {updated}, deleted: {deleted})" - logger.info("[GenerateSiteStructure] %s for blueprint %s", message, blueprint.id) - - return { - 'success': True, - 'count': created + updated, - 'site_blueprint_id': blueprint.id, - 'pages_created': created, - 'pages_updated': updated, - 'pages_deleted': deleted, - } - - # Helpers ----------------------------------------------------------------- - - def _ensure_dict(self, data: Any) -> Dict[str, Any]: - if isinstance(data, dict): - return data - raise ValueError("AI response must be a JSON object with site metadata") - - def _extract_json_object(self, text: str) -> str: - start = text.find('{') - end = text.rfind('}') - if start != -1 and end != -1 and end > start: - return text[start:end + 1] - return '' - - def _sync_page_blueprints(self, blueprint: SiteBlueprint, pages: List[Dict[str, Any]]) -> Tuple[int, int, int]: - existing = {page.slug: page for page in blueprint.pages.all()} - seen_slugs = set() - created = updated = 0 - - for order, page_data in enumerate(pages or []): - slug = page_data.get('slug') or page_data.get('id') or page_data.get('title') or f"page-{order + 1}" - slug = slugify(slug) or f"page-{order + 1}" - seen_slugs.add(slug) - - defaults = { - 'title': page_data.get('title') or page_data.get('name') or slug.replace('-', ' ').title(), - 'type': self._map_page_type(page_data.get('type')), - 'blocks_json': page_data.get('blocks') or page_data.get('sections') or [], - 'status': page_data.get('status') or 'draft', - 'order': order, - } - - page_obj, created_flag = PageBlueprint.objects.update_or_create( - site_blueprint=blueprint, - slug=slug, - defaults=defaults - ) - if created_flag: - created += 1 - else: - updated += 1 - - # Delete pages not present in new structure - deleted = 0 - for slug, page in existing.items(): - if slug not in seen_slugs: - page.delete() - deleted += 1 - - return created, updated, deleted - - def _map_page_type(self, page_type: Any) -> str: - allowed = {choice[0] for choice in PageBlueprint._meta.get_field('type').choices} - if isinstance(page_type, str): - normalized = page_type.lower() - if normalized in allowed: - return normalized - # Map friendly names - mapping = { - 'homepage': 'home', - 'landing': 'home', - 'service': 'services', - 'product': 'products', - } - mapped = mapping.get(normalized) - if mapped in allowed: - return mapped - return 'custom' - diff --git a/backend/igny8_core/ai/registry.py b/backend/igny8_core/ai/registry.py index 2702cbf3..59b41274 100644 --- a/backend/igny8_core/ai/registry.py +++ b/backend/igny8_core/ai/registry.py @@ -94,11 +94,6 @@ def _load_generate_image_prompts(): from igny8_core.ai.functions.generate_image_prompts import GenerateImagePromptsFunction return GenerateImagePromptsFunction -def _load_generate_site_structure(): - """Lazy loader for generate_site_structure function""" - from igny8_core.ai.functions.generate_site_structure import GenerateSiteStructureFunction - return GenerateSiteStructureFunction - def _load_optimize_content(): """Lazy loader for optimize_content function""" from igny8_core.ai.functions.optimize_content import OptimizeContentFunction @@ -109,6 +104,5 @@ register_lazy_function('generate_ideas', _load_generate_ideas) register_lazy_function('generate_content', _load_generate_content) register_lazy_function('generate_images', _load_generate_images) register_lazy_function('generate_image_prompts', _load_generate_image_prompts) -register_lazy_function('generate_site_structure', _load_generate_site_structure) register_lazy_function('optimize_content', _load_optimize_content) diff --git a/backend/igny8_core/business/automation/migrations/0001_initial.py b/backend/igny8_core/business/automation/migrations/0001_initial.py index a291a95a..2e0e7dee 100644 --- a/backend/igny8_core/business/automation/migrations/0001_initial.py +++ b/backend/igny8_core/business/automation/migrations/0001_initial.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('system', '__latest__'), + ('igny8_core_auth', '__latest__'), ] operations = [ @@ -35,11 +35,11 @@ class Migration(migrations.Migration): ('next_run_at', models.DateTimeField(blank=True, null=True)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.account')), - ('site', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='automation_config', to='system.site')), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='igny8_core_auth.account')), + ('site', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='automation_config', to='igny8_core_auth.site')), ], options={ - 'db_table': 'automation_config', + 'db_table': 'igny8_automation_configs', }, ), migrations.CreateModel( @@ -74,11 +74,11 @@ class Migration(migrations.Migration): ('error_message', models.TextField(blank=True, null=True)), ('started_at', models.DateTimeField(auto_now_add=True)), ('completed_at', models.DateTimeField(blank=True, null=True)), - ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.account')), - ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='automation_runs', to='system.site')), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='igny8_core_auth.account')), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='automation_runs', to='igny8_core_auth.site')), ], options={ - 'db_table': 'automation_run', + 'db_table': 'igny8_automation_runs', 'ordering': ['-started_at'], 'indexes': [ models.Index(fields=['site', 'status'], name='automation_site_status_idx'), diff --git a/backend/igny8_core/business/automation/services/automation_service.py b/backend/igny8_core/business/automation/services/automation_service.py index bab1fc83..a44ba2bd 100644 --- a/backend/igny8_core/business/automation/services/automation_service.py +++ b/backend/igny8_core/business/automation/services/automation_service.py @@ -760,14 +760,14 @@ class AutomationService: def estimate_credits(self) -> int: """Estimate total credits needed for automation""" # Count items - keywords_count = Keywords.objects.filter(site=self.site, status='new', cluster__isnull=True).count() + keywords_count = Keywords.objects.filter(site=self.site, status='new', cluster__isnull=True, disabled=False).count() clusters_count = Clusters.objects.filter(site=self.site, status='new').exclude(ideas__isnull=False).count() ideas_count = ContentIdeas.objects.filter(site=self.site, status='new').count() - tasks_count = Tasks.objects.filter(site=self.site, status='queued', content__isnull=True).count() + tasks_count = Tasks.objects.filter(site=self.site, status='queued').count() content_count = Content.objects.filter(site=self.site, status='draft').annotate(images_count=Count('images')).filter(images_count=0).count() # Estimate credits - clustering_credits = (keywords_count // 5) + 1 # 1 credit per 5 keywords + clustering_credits = (keywords_count // 5) + 1 if keywords_count > 0 else 0 # 1 credit per 5 keywords ideas_credits = clusters_count * 2 # 2 credits per cluster content_credits = tasks_count * 5 # Assume 2500 words avg = 5 credits prompts_credits = content_count * 2 # Assume 4 prompts per content = 2 credits diff --git a/backend/igny8_core/business/automation/views.py b/backend/igny8_core/business/automation/views.py index 18c0e967..fbb42d27 100644 --- a/backend/igny8_core/business/automation/views.py +++ b/backend/igny8_core/business/automation/views.py @@ -27,7 +27,7 @@ class AutomationViewSet(viewsets.ViewSet): status=status.HTTP_400_BAD_REQUEST ) - site = get_object_or_404(Site, id=site_id, account__user=request.user) + site = get_object_or_404(Site, id=site_id, account=request.user.account) return site, None @action(detail=False, methods=['get']) @@ -308,6 +308,6 @@ class AutomationViewSet(viewsets.ViewSet): return Response({ 'estimated_credits': estimated_credits, - 'current_balance': site.account.credits_balance, - 'sufficient': site.account.credits_balance >= (estimated_credits * 1.2) + 'current_balance': site.account.credits, + 'sufficient': site.account.credits >= (estimated_credits * 1.2) }) diff --git a/backend/igny8_core/business/publishing/tests/test_deployment_service.py b/backend/igny8_core/business/publishing/tests/test_deployment_service.py index 79c2e91b..29d81256 100644 --- a/backend/igny8_core/business/publishing/tests/test_deployment_service.py +++ b/backend/igny8_core/business/publishing/tests/test_deployment_service.py @@ -1,18 +1,17 @@ """ -Tests for DeploymentService +DEPRECATED: Tests for DeploymentService - SiteBlueprint models removed Phase 5: Sites Renderer & Publishing """ from django.test import TestCase from django.utils import timezone from igny8_core.auth.models import Account, Site, Sector, User, Plan, Industry, IndustrySector -from igny8_core.business.site_building.models import SiteBlueprint from igny8_core.business.publishing.models import DeploymentRecord from igny8_core.business.publishing.services.deployment_service import DeploymentService class DeploymentServiceTestCase(TestCase): - """Test cases for DeploymentService""" + """DEPRECATED: Test cases for DeploymentService""" def setUp(self): """Set up test data""" @@ -69,14 +68,8 @@ class DeploymentServiceTestCase(TestCase): name="Test Sector", slug="test-sector" ) - self.blueprint = SiteBlueprint.objects.create( - account=self.account, - site=self.site, - sector=self.sector, - name="Test Blueprint", - status='ready', - version=1 - ) + # DEPRECATED: SiteBlueprint model removed + self.blueprint = None self.service = DeploymentService() def test_get_status_returns_deployed_record(self): diff --git a/backend/igny8_core/business/site_building/models.py b/backend/igny8_core/business/site_building/models.py index 5062ed69..51934985 100644 --- a/backend/igny8_core/business/site_building/models.py +++ b/backend/igny8_core/business/site_building/models.py @@ -1,9 +1,10 @@ """ Site Building Models Legacy SiteBuilder module has been removed. -This file is kept for backwards compatibility with migrations. +This file is kept for backwards compatibility with migrations and legacy code. """ from django.db import models +from igny8_core.auth.models import AccountBaseModel # All SiteBuilder models have been removed: # - SiteBlueprint @@ -13,3 +14,32 @@ from django.db import models # - BusinessType, AudienceProfile, BrandPersonality, HeroImageryDirection # # Taxonomy functionality moved to ContentTaxonomy model in business/content/models.py + +# Stub classes for backwards compatibility with legacy imports +class SiteBlueprint(AccountBaseModel): + """Legacy stub - SiteBuilder has been removed""" + class Meta: + app_label = 'site_building' + db_table = 'legacy_site_blueprint_stub' + managed = False # Don't create table + +class PageBlueprint(AccountBaseModel): + """Legacy stub - SiteBuilder has been removed""" + class Meta: + app_label = 'site_building' + db_table = 'legacy_page_blueprint_stub' + managed = False # Don't create table + +class SiteBlueprintCluster(AccountBaseModel): + """Legacy stub - SiteBuilder has been removed""" + class Meta: + app_label = 'site_building' + db_table = 'legacy_site_blueprint_cluster_stub' + managed = False # Don't create table + +class SiteBlueprintTaxonomy(AccountBaseModel): + """Legacy stub - SiteBuilder has been removed""" + class Meta: + app_label = 'site_building' + db_table = 'legacy_site_blueprint_taxonomy_stub' + managed = False # Don't create table diff --git a/backend/igny8_core/business/site_building/tests/base.py b/backend/igny8_core/business/site_building/tests/base.py index 50d65eb9..6a19a45c 100644 --- a/backend/igny8_core/business/site_building/tests/base.py +++ b/backend/igny8_core/business/site_building/tests/base.py @@ -13,13 +13,12 @@ from igny8_core.auth.models import ( Site, User, ) -from igny8_core.business.site_building.models import PageBlueprint, SiteBlueprint class SiteBuilderTestBase(TestCase): """ - Provides a lightweight set of fixtures (account/site/sector/blueprint) - so Site Builder tests can focus on service logic instead of boilerplate. + DEPRECATED: Provides a lightweight set of fixtures (account/site/sector/blueprint) + SiteBlueprint models have been removed. """ def setUp(self): @@ -65,20 +64,9 @@ class SiteBuilderTestBase(TestCase): account=self.account, ) - self.blueprint = SiteBlueprint.objects.create( - site=self.site, - sector=self.sector, - name='Core Blueprint', - description='Initial blueprint used for tests', - hosting_type='igny8_sites', - config_json={ - 'business_brief': 'Default brief', - 'objectives': ['Drive demos'], - 'style': {'palette': 'bold'}, - }, - ) - self.page_blueprint = PageBlueprint.objects.create( - site_blueprint=self.blueprint, + # DEPRECATED: SiteBlueprint and PageBlueprint models removed + self.blueprint = None + self.page_blueprint = None slug='home', title='Home', type='home', diff --git a/backend/igny8_core/business/site_building/tests/test_bulk_generation.py b/backend/igny8_core/business/site_building/tests/test_bulk_generation.py index bb27665c..dcaffb3e 100644 --- a/backend/igny8_core/business/site_building/tests/test_bulk_generation.py +++ b/backend/igny8_core/business/site_building/tests/test_bulk_generation.py @@ -1,20 +1,18 @@ """ -Tests for Bulk Page Generation +DEPRECATED: Tests for Bulk Page Generation - SiteBlueprint models removed Phase 5: Sites Renderer & Bulk Generation """ from django.test import TestCase from unittest.mock import patch, Mock from igny8_core.auth.models import Account, Site, Sector -from igny8_core.business.site_building.models import SiteBlueprint, PageBlueprint -from igny8_core.business.site_building.services.page_generation_service import PageGenerationService from igny8_core.business.content.models import Tasks from .base import SiteBuilderTestBase class BulkGenerationTestCase(SiteBuilderTestBase): - """Test cases for bulk page generation""" + """DEPRECATED: Test cases for bulk page generation""" def setUp(self): """Set up test data""" diff --git a/backend/igny8_core/urls.py b/backend/igny8_core/urls.py index d2458869..6f3801ac 100644 --- a/backend/igny8_core/urls.py +++ b/backend/igny8_core/urls.py @@ -52,3 +52,9 @@ urlpatterns = [ path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), ] + +# Error handlers +handler400 = 'django.views.defaults.bad_request' +handler403 = 'django.views.defaults.permission_denied' +handler404 = 'django.views.defaults.page_not_found' +handler500 = 'django.views.defaults.server_error' diff --git a/frontend/src/components/Automation/ActivityLog.tsx b/frontend/src/components/Automation/ActivityLog.tsx index d9541d77..d324cd6c 100644 --- a/frontend/src/components/Automation/ActivityLog.tsx +++ b/frontend/src/components/Automation/ActivityLog.tsx @@ -59,6 +59,3 @@ const ActivityLog: React.FC = ({ runId }) => { }; export default ActivityLog; -}; - -export default ActivityLog; diff --git a/frontend/src/components/Automation/RunHistory.tsx b/frontend/src/components/Automation/RunHistory.tsx index a908bf67..9b5768fa 100644 --- a/frontend/src/components/Automation/RunHistory.tsx +++ b/frontend/src/components/Automation/RunHistory.tsx @@ -118,6 +118,3 @@ const RunHistory: React.FC = ({ siteId }) => { }; export default RunHistory; -}; - -export default RunHistory;