diff --git a/IGNY8-APP.md b/IGNY8-APP.md index 857610a2..9f9e63c8 100644 --- a/IGNY8-APP.md +++ b/IGNY8-APP.md @@ -246,8 +246,21 @@ Stage 7: Generate images → Review queue | **Usage** | ✅ Active | `/account/usage` | | **AI Models** | ✅ Active (Admin) | `/settings/integration` | | **Help** | ✅ Active | `/help` | -| **Linker** | ⏸️ Disabled | Internal linking (available, disabled by default) | -| **Optimizer** | ⏸️ Disabled | Content optimization (available, disabled by default) | +| **SiteBuilder** | ❌ Deprecated | Removed - was for site structure generation | +| **Linker** | ⏸️ Phase 2 | Internal linking suggestions (disabled by default) | +| **Optimizer** | ⏸️ Phase 2 | Content optimization (disabled by default) | + +### Module Status Details + +| Module | Status | Notes | +|--------|--------|-------| +| **SiteBuilder** | ❌ Deprecated | Code exists but feature is removed. Marked for cleanup. | +| **Linker** | ⏸️ Phase 2 | Feature flag: `linker_enabled`. Available but disabled by default. | +| **Optimizer** | ⏸️ Phase 2 | Feature flag: `optimizer_enabled`. Available but disabled by default. | + +To enable Phase 2 modules, update via Django Admin: +- `GlobalModuleSettings` (pk=1) for platform-wide settings +- `ModuleEnableSettings` for per-account settings --- diff --git a/backend/igny8_core/ai/ai_core.py b/backend/igny8_core/ai/ai_core.py index aa1d2a65..3248f9aa 100644 --- a/backend/igny8_core/ai/ai_core.py +++ b/backend/igny8_core/ai/ai_core.py @@ -591,7 +591,11 @@ class AICore: image_url = image_data.get('url') revised_prompt = image_data.get('revised_prompt') - cost = IMAGE_MODEL_RATES.get(model, 0.040) * n + # Use ModelRegistry for image cost (with fallback to constants) + from igny8_core.ai.model_registry import ModelRegistry + cost = float(ModelRegistry.calculate_cost(model, num_images=n)) + if cost == 0: + cost = IMAGE_MODEL_RATES.get(model, 0.040) * n print(f"[AI][{function_name}] Step 5: Image generated successfully") print(f"[AI][{function_name}] Step 6: Cost: ${cost:.4f}") print(f"[AI][{function_name}][Success] Image generation completed") @@ -827,15 +831,24 @@ class AICore: } def calculate_cost(self, model: str, input_tokens: int, output_tokens: int, model_type: str = 'text') -> float: - """Calculate cost for API call""" + """Calculate cost for API call using ModelRegistry with fallback to constants""" + from igny8_core.ai.model_registry import ModelRegistry + if model_type == 'text': - rates = MODEL_RATES.get(model, {'input': 2.00, 'output': 8.00}) - input_cost = (input_tokens / 1_000_000) * rates['input'] - output_cost = (output_tokens / 1_000_000) * rates['output'] - return input_cost + output_cost + cost = float(ModelRegistry.calculate_cost(model, input_tokens=input_tokens, output_tokens=output_tokens)) + if cost == 0: + # Fallback to constants + rates = MODEL_RATES.get(model, {'input': 2.00, 'output': 8.00}) + input_cost = (input_tokens / 1_000_000) * rates['input'] + output_cost = (output_tokens / 1_000_000) * rates['output'] + return input_cost + output_cost + return cost elif model_type == 'image': - rate = IMAGE_MODEL_RATES.get(model, 0.040) - return rate * 1 + cost = float(ModelRegistry.calculate_cost(model, num_images=1)) + if cost == 0: + rate = IMAGE_MODEL_RATES.get(model, 0.040) + return rate * 1 + return cost return 0.0 # Legacy method names for backward compatibility diff --git a/backend/igny8_core/business/planning/models.py b/backend/igny8_core/business/planning/models.py index 42adf528..8dbdf76c 100644 --- a/backend/igny8_core/business/planning/models.py +++ b/backend/igny8_core/business/planning/models.py @@ -1,6 +1,9 @@ from django.db import models from igny8_core.auth.models import SiteSectorBaseModel, SeedKeyword from igny8_core.common.soft_delete import SoftDeletableModel, SoftDeleteManager +import logging + +logger = logging.getLogger(__name__) class Clusters(SoftDeletableModel, SiteSectorBaseModel): @@ -39,6 +42,27 @@ class Clusters(SoftDeletableModel, SiteSectorBaseModel): def __str__(self): return self.name + + def soft_delete(self, user=None, reason=None, retention_days=None): + """ + Override soft_delete to cascade status reset to related Keywords. + When a cluster is deleted, its keywords should: + - Have their cluster FK set to NULL (handled by SET_NULL) + - Have their status reset to 'new' (orphaned keywords) + """ + # Reset related keywords status to 'new' and clear cluster FK + keywords_count = self.keywords.filter(is_deleted=False).update( + cluster=None, + status='new' + ) + + logger.info( + f"[Clusters.soft_delete] Cluster {self.id} '{self.name}' cascade: " + f"reset {keywords_count} keywords to status='new'" + ) + + # Call parent soft_delete + super().soft_delete(user=user, reason=reason, retention_days=retention_days) class Keywords(SoftDeletableModel, SiteSectorBaseModel): diff --git a/frontend/DESIGN_SYSTEM.md b/frontend/DESIGN_SYSTEM.md index dcb56507..8cc56762 100644 --- a/frontend/DESIGN_SYSTEM.md +++ b/frontend/DESIGN_SYSTEM.md @@ -146,5 +146,70 @@ Before implementing any UI element: --- -**Last Updated**: Current Session +## 📝 Typography Scale + +Typography tokens are defined in `index.css` and should be used consistently: + +### Title Sizes (for page/section headings) +| Token | Size | Line Height | Use Case | +|-------|------|-------------|----------| +| `--text-title-2xl` | 72px | 90px | Hero sections only | +| `--text-title-xl` | 60px | 72px | Landing page headings | +| `--text-title-lg` | 48px | 60px | Major section headers | +| `--text-title-md` | 36px | 44px | Page titles | +| `--text-title-sm` | 30px | 38px | Section subtitles | + +### Theme Sizes (for body text) +| Token | Size | Line Height | Use Case | +|-------|------|-------------|----------| +| `--text-theme-xl` | 20px | 30px | Large body text, intro paragraphs | +| `--text-theme-sm` | 14px | 20px | Standard body text, menu items | +| `--text-theme-xs` | 12px | 18px | Small text, labels, captions | + +### Usage in Tailwind +```tsx +// Use Tailwind utilities mapped to tokens +

Page Title

+

Body text

+Caption + +// Or use the custom utility classes +

Section Title

+

Body text

+``` + +### Font Weights +- `font-normal` (400) - Body text +- `font-medium` (500) - Labels, menu items +- `font-semibold` (600) - Headings, emphasis +- `font-bold` (700) - Strong emphasis + +--- + +## 🎨 Module Colors + +Module-specific colors are defined in `src/config/colors.config.ts`: + +| Module | Primary Color | Usage | +|--------|---------------|-------| +| Keywords | `brand-500` (blue) | Icons, progress bars, badges | +| Clusters | `purple-500` | Icons, progress bars, badges | +| Ideas | `purple-600` | Icons, progress bars, badges | +| Tasks | `success-600` (green) | Icons, progress bars, badges | +| Content | `success-500` | Icons, progress bars, badges | +| Images | `purple-500` | Icons, progress bars, badges | +| Automation | `brand-500` | Pipeline cards | +| Billing | `warning-500` (amber) | Credit displays | + +```tsx +import { MODULE_COLORS } from '@/config/colors.config'; + +// Use in components +
Keywords Section
+Cluster Label +``` + +--- + +**Last Updated**: December 30, 2025 **Status**: Active Design System Rules diff --git a/frontend/src/index.css b/frontend/src/index.css index 2001afc3..f08a0339 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -175,7 +175,7 @@ } @utility menu-item { - @apply relative flex items-center w-full gap-3 px-3.5 py-2.5 font-medium rounded-lg text-theme-sm; + @apply relative flex items-center w-full gap-3.5 px-4 py-3 font-medium rounded-lg text-theme-sm; } @utility menu-item-active { @@ -188,7 +188,7 @@ /* Menu icon sizing - consistent across sidebar */ @utility menu-item-icon-size { - @apply w-5 h-5 flex-shrink-0; + @apply w-6 h-6 flex-shrink-0; /* Force SVG icons to inherit parent size */ & svg { @@ -207,7 +207,7 @@ /* Dropdown menu items - increased spacing */ @utility menu-dropdown-item { - @apply block px-3 py-2 text-theme-sm font-medium rounded-md transition-colors; + @apply block px-3.5 py-2.5 text-theme-sm font-medium rounded-md transition-colors; } @utility menu-dropdown-item-active {