From 0bd603f9254145ba0654577bdf2bd2b427c109ee Mon Sep 17 00:00:00 2001 From: alorig <220087330+alorig@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:52:43 +0500 Subject: [PATCH] docs --- CHANGELOG.md | 61 ++ IMPLEMENTATION_AUDIT_REPORT.md | 978 +++++++++++++++++++++ planner-writer-workflow.md | 1466 ++++++++++++++++++++++++++++++++ 3 files changed, 2505 insertions(+) create mode 100644 IMPLEMENTATION_AUDIT_REPORT.md create mode 100644 planner-writer-workflow.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f5cce9..3a92c315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,67 @@ Each entry follows this format: --- +## [1.0.1] - 2025-11-24 + +### Changed + +#### WordPress Integration - Authentication Modernization +- **Simplified authentication to API key only** across WordPress plugin and Django backend + - Removed webhook secret/signature validation in favor of direct API key authentication + - Updated `class-igny8-webhooks.php` to validate `X-IGNY8-API-KEY` or `Authorization: Bearer` headers + - Modified Django `views.py` validator to require only `api_key` in `credentials_json` for new WordPress integrations + - Files changed: + - `igny8-wp-integration/includes/class-igny8-webhooks.php` + - `igny8/backend/igny8_core/modules/integration/views.py` + +- **Implemented 3-state connection model** for clearer integration status tracking + - States: `not_connected` (no API key), `configured` (API key + integration_id), `connected` (structure synced) + - Added `igny8_get_connection_state()` helper function + - Files changed: + - `igny8-wp-integration/includes/functions.php` + - `igny8-wp-integration/admin/settings.php` + +### Added + +#### WordPress Integration - Enhanced Logging & Filtering +- **Safe logging functions** that filter sensitive data (api_key, password, secret, token) + - Added `igny8_log_connection_state()` for connection state transitions + - Added `igny8_log_sync()` for sync operations with context tracking + - Enhanced `igny8_sync_site_structure_to_backend()` with detailed operation logging + - Files changed: + - `igny8-wp-integration/includes/functions.php` + +- **Product attribute filtering** to sync only IGNY8-created WooCommerce attributes + - Filters product attribute terms by `igny8_origin = 'igny8_app'` term meta + - Applies to taxonomies starting with `pa_` prefix + - Files changed: + - `igny8-wp-integration/data/site-collection.php` + +### Removed + +#### WordPress Integration - Legacy Authentication +- **Removed webhook secret authentication** from WordPress plugin + - Deleted `igny8_get_webhook_secret()` and `igny8_regenerate_webhook_secret()` functions + - Removed webhook secret UI from admin settings page + - Removed HMAC-SHA256 signature validation from webhook handler + - Files changed: + - `igny8-wp-integration/includes/functions.php` + - `igny8-wp-integration/includes/class-igny8-webhooks.php` + - `igny8-wp-integration/admin/settings.php` + +### Security + +#### WordPress Integration +- **Upgraded authentication security** by consolidating to single API key method + - Eliminated webhook signature complexity and potential timing attack vectors + - Implemented constant-time comparison for API key validation using `hash_equals()` + - All incoming webhook requests now require valid API key in headers + - Files affected: + - `igny8-wp-integration/includes/class-igny8-webhooks.php` + - `igny8-wp-integration/includes/class-igny8-rest-api.php` + +--- + ## [1.0.0] - 2025-11-24 ### Added diff --git a/IMPLEMENTATION_AUDIT_REPORT.md b/IMPLEMENTATION_AUDIT_REPORT.md new file mode 100644 index 00000000..93f9c518 --- /dev/null +++ b/IMPLEMENTATION_AUDIT_REPORT.md @@ -0,0 +1,978 @@ +# IGNY8 Implementation Audit Report +**Date:** December 2024 +**Auditor:** GitHub Copilot (Claude Sonnet 4.5) +**Scope:** IGNY8 Cluster + Site Refactor Plan (Nov 24) - IGNY8 App Only + +--- + +## Executive Summary + +This audit reviews the **IGNY8 application** (frontend React/TypeScript + backend Django/Python) implementation against the "IGNY8 Cluster + Site Refactor Plan (Nov 24)" specifications. The WordPress plugin is **excluded** from this audit except where it integrates with IGNY8 for content import/sync. + +**Overall Status:** πŸ”΄ **MAJOR GAPS IDENTIFIED** (~33% Complete) + +- βœ… **Fully Implemented:** 5 features (33%) +- 🟑 **Partially Implemented:** 3 features (20%) +- ❌ **Not Implemented:** 7 features (47%) + +**Critical Issues:** +1. ❌ Cluster detail page missing (`/planner/clusters/:id` route doesn't exist) +2. ❌ Sites page UI not refactored (Builder/Blueprints buttons still visible) +3. ❌ Settings page SEO tabs not merged into 2x2 grid +4. ❌ Linker and Optimizer still visible in sidebar navigation + +--- + +## Detailed Feature Audit + +### 1. Persistent Login (localStorage) + +**Status:** βœ… **FULLY IMPLEMENTED** + +**Implementation Details:** +- **File:** `frontend/src/store/authStore.ts` +- **Lines:** 1-283 +- **Implementation:** Zustand store with `persist` middleware +- **Storage Backend:** `localStorage` with key `auth-storage` +- **Persisted State:** + - `user` (User object with account/plan/role) + - `token` (JWT access token) + - `refreshToken` (JWT refresh token) + - `isAuthenticated` (boolean flag) + +**Code Evidence:** +```typescript +// frontend/src/store/authStore.ts +export const useAuthStore = create()( + persist( + (set, get) => ({ + user: null, + token: null, + refreshToken: null, + isAuthenticated: false, + // ... auth actions: login, logout, refreshUser, refreshToken + }), + { + name: 'auth-storage', + partialize: (state) => ({ + user: state.user, + token: state.token, + refreshToken: state.refreshToken, + isAuthenticated: state.isAuthenticated, + }), + } + ) +); +``` + +**Verification:** +- βœ… Uses Zustand `persist` middleware +- βœ… Stores token and refreshToken in localStorage +- βœ… `refreshUser()` method validates session on app load +- βœ… Auto-logout on token expiration + +**Recommendation:** βœ… **No changes needed** + +--- + +### 2. Writer Task Fields (`entity_type`, `cluster_role`) + +**Status:** βœ… **FULLY IMPLEMENTED** + +**Implementation Details:** +- **File:** `backend/igny8_core/business/content/models.py` +- **Model:** `Tasks` +- **Lines:** 6-97 + +**Fields Implemented:** +1. βœ… `entity_type` - CharField with choices (post, page, product, service, taxonomy_term) +2. βœ… `cluster_role` - CharField with choices (hub, supporting, attribute) +3. βœ… `cluster` - ForeignKey to `planner.Clusters` +4. βœ… `keyword_objects` - ManyToManyField to `planner.Keywords` +5. βœ… `taxonomy` - ForeignKey to `site_building.SiteBlueprintTaxonomy` + +**Code Evidence:** +```python +# backend/igny8_core/business/content/models.py +class Tasks(SiteSectorBaseModel): + ENTITY_TYPE_CHOICES = [ + ('post', 'Post'), + ('page', 'Page'), + ('product', 'Product'), + ('service', 'Service'), + ('taxonomy_term', 'Taxonomy Term'), + ] + + CLUSTER_ROLE_CHOICES = [ + ('hub', 'Hub'), + ('supporting', 'Supporting'), + ('attribute', 'Attribute'), + ] + + entity_type = models.CharField( + max_length=50, + choices=ENTITY_TYPE_CHOICES, + default='post', + db_index=True, + help_text="Type of content entity" + ) + + cluster_role = models.CharField( + max_length=50, + choices=CLUSTER_ROLE_CHOICES, + default='hub', + help_text="Role within the cluster-driven sitemap" + ) + + cluster = models.ForeignKey( + 'planner.Clusters', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='tasks' + ) +``` + +**Verification:** +- βœ… `entity_type` field exists with correct choices +- βœ… `cluster_role` field exists with correct choices +- βœ… Database indexes on both fields +- βœ… Proper foreign key relationships to Clusters + +**Recommendation:** βœ… **No changes needed** + +--- + +### 3. Content Model Fields (`entity_type`, `cluster_role`, `sync_status`) + +**Status:** βœ… **FULLY IMPLEMENTED** + +**Implementation Details:** +- **File:** `backend/igny8_core/business/content/models.py` +- **Model:** `Content` +- **Lines:** 100-293 + +**Fields Implemented:** +1. βœ… `entity_type` - CharField with choices (post, page, product, service, taxonomy_term, legacy types) +2. βœ… `cluster_role` - CharField with choices (hub, supporting, attribute) +3. βœ… `sync_status` - CharField with choices (native, imported, synced) +4. βœ… `source` - CharField (igny8, wordpress, shopify, custom) +5. βœ… `external_id`, `external_url`, `external_type` - for WP integration +6. βœ… `structure_data` - JSONField for content metadata +7. βœ… `json_blocks` - JSONField for structured content +8. βœ… `cluster` - ForeignKey to `planner.Clusters` +9. βœ… `taxonomies` - ManyToManyField to `ContentTaxonomy` + +**Code Evidence:** +```python +# backend/igny8_core/business/content/models.py +class Content(SiteSectorBaseModel): + ENTITY_TYPE_CHOICES = [ + ('post', 'Blog Post'), + ('page', 'Page'), + ('product', 'Product'), + ('service', 'Service Page'), + ('taxonomy_term', 'Taxonomy Term Page'), + # Legacy choices for backward compatibility + ('blog_post', 'Blog Post (Legacy)'), + ('article', 'Article (Legacy)'), + ('taxonomy', 'Taxonomy Page (Legacy)'), + ] + + SYNC_STATUS_CHOICES = [ + ('native', 'Native IGNY8 Content'), + ('imported', 'Imported from External'), + ('synced', 'Synced from External'), + ] + + CLUSTER_ROLE_CHOICES = [ + ('hub', 'Hub Page'), + ('supporting', 'Supporting Content'), + ('attribute', 'Attribute Page'), + ] + + entity_type = models.CharField( + max_length=50, + choices=ENTITY_TYPE_CHOICES, + default='post', + db_index=True + ) + + sync_status = models.CharField( + max_length=50, + choices=SYNC_STATUS_CHOICES, + default='native', + db_index=True + ) + + cluster_role = models.CharField( + max_length=50, + choices=CLUSTER_ROLE_CHOICES, + default='supporting', + blank=True, + null=True, + db_index=True + ) + + structure_data = models.JSONField( + default=dict, + blank=True, + help_text="Content structure data (metadata, schema, etc.)" + ) +``` + +**Verification:** +- βœ… `entity_type` field with full choices (incl. legacy) +- βœ… `sync_status` field for tracking import source +- βœ… `cluster_role` field for cluster hierarchy +- βœ… `structure_data` JSONField for flexible metadata +- βœ… Proper indexing on all key fields + +**Recommendation:** βœ… **No changes needed** + +--- + +### 4. Cluster Detail Page (`/planner/clusters/:id`) + +**Status:** ❌ **NOT IMPLEMENTED** + +**Expected Implementation:** +- Route: `/planner/clusters/:id` +- Component: `frontend/src/pages/Planner/ClusterDetail.tsx` (doesn't exist) +- Features: View cluster metadata, keywords, tasks, content ideas + +**Current State:** +- **Route:** ❌ NOT DEFINED in `App.tsx` +- **Component:** ❌ DOES NOT EXIST +- **Navigation:** ❌ Cluster names in table are NOT clickable +- **Workaround:** PostEditor.tsx line 820 has navigate to `/planner/clusters/${cluster_id}` but route doesn't exist + +**Code Evidence:** +```tsx +// frontend/src/App.tsx - Lines 200-221 +// Planner routes - NO cluster detail route exists +} /> + + + + + +} /> + + + {/* This is the TABLE view only */} + + +} /> + + + + + +} /> +// ❌ NO +``` + +```tsx +// frontend/src/pages/Planner/Clusters.tsx - Lines 1-450 +// Clusters page is TABLE-ONLY, no detail view +export default function Clusters() { + // ... table configuration only + // ❌ NO cluster name click handler to navigate to detail page + // ❌ NO detail view component +} +``` + +**Affected Files:** +1. ❌ `App.tsx` - Missing route definition +2. ❌ `pages/Planner/ClusterDetail.tsx` - Component doesn't exist +3. 🟑 `pages/Planner/Clusters.tsx` - Table exists but no clickable names +4. ⚠️ `pages/Sites/PostEditor.tsx:820` - Has broken link to cluster detail + +**Missing Functionality:** +- ❌ View cluster metadata (name, description, context_type, dimension_meta) +- ❌ List all keywords in cluster with stats (volume, difficulty, status) +- ❌ List all content ideas linked to cluster +- ❌ List all tasks/content linked to cluster +- ❌ Edit cluster details (name, description, context_type) +- ❌ Add/remove keywords from cluster +- ❌ Generate new ideas from cluster keywords + +**Recommendation:** πŸ”΄ **CRITICAL - CREATE CLUSTER DETAIL PAGE** + +**Implementation Steps:** +1. Create `frontend/src/pages/Planner/ClusterDetail.tsx` +2. Add route in `App.tsx`: `} />` +3. Make cluster names clickable in `Clusters.tsx` table +4. Create API endpoints: `GET /v1/planner/clusters/:id/keywords/`, `/ideas/`, `/tasks/` +5. Add tabs: Overview, Keywords, Ideas, Tasks, Settings + +--- + +### 5. Cluster Model Fields (`context_type`, `dimension_meta`) + +**Status:** βœ… **FULLY IMPLEMENTED** + +**Implementation Details:** +- **File:** `backend/igny8_core/business/planning/models.py` +- **Model:** `Clusters` +- **Lines:** 5-52 + +**Fields Implemented:** +1. βœ… `context_type` - CharField with choices (topic, attribute, service_line) +2. βœ… `dimension_meta` - JSONField for extended metadata +3. βœ… Proper indexes and database constraints + +**Code Evidence:** +```python +# backend/igny8_core/business/planning/models.py +class Clusters(SiteSectorBaseModel): + CONTEXT_TYPE_CHOICES = [ + ('topic', 'Topic Cluster'), + ('attribute', 'Attribute Cluster'), + ('service_line', 'Service Line'), + ] + + name = models.CharField(max_length=255, unique=True, db_index=True) + description = models.TextField(blank=True, null=True) + keywords_count = models.IntegerField(default=0) + volume = models.IntegerField(default=0) + mapped_pages = models.IntegerField(default=0) + status = models.CharField(max_length=50, default='active') + + context_type = models.CharField( + max_length=50, + choices=CONTEXT_TYPE_CHOICES, + default='topic', + help_text="Primary dimension for this cluster" + ) + + dimension_meta = models.JSONField( + default=dict, + blank=True, + help_text="Extended metadata (taxonomy hints, attribute suggestions, coverage targets)" + ) +``` + +**Verification:** +- βœ… `context_type` field with choices +- βœ… `dimension_meta` JSONField for flexible metadata +- βœ… Database index on `context_type` +- βœ… Proper default values + +**Recommendation:** βœ… **No changes needed** + +--- + +### 6. Site Builder Hidden from Navigation + +**Status:** ❌ **NOT IMPLEMENTED** + +**Expected Implementation:** +- Remove "Create with Builder" button from Sites page +- Remove "Blueprints" navigation tab +- Hide Blueprint-related functionality + +**Current State:** +- ❌ "Create with Builder" button STILL VISIBLE +- ❌ "Blueprints" tab STILL in navigation +- ❌ Blueprint routes still active + +**Code Evidence:** +```tsx +// frontend/src/pages/Sites/List.tsx - Lines 688-689 +const tabItems = [ + { label: 'Sites', path: '/sites', icon: }, + { label: 'Create Site', path: '/sites/builder', icon: }, // ❌ SHOULD BE REMOVED + { label: 'Blueprints', path: '/sites/blueprints', icon: }, // ❌ SHOULD BE REMOVED +]; + +// Lines 717-721 +
+ + +
+``` + +**Affected Files:** +1. `frontend/src/pages/Sites/List.tsx` - Lines 688-689, 717-721 +2. `frontend/src/pages/Sites/DeploymentPanel.tsx` - Still uses `fetchSiteBlueprints` +3. `frontend/src/App.tsx` - May have `/sites/builder` and `/sites/blueprints` routes + +**Recommendation:** πŸ”΄ **CRITICAL - REMOVE BUILDER UI** + +**Implementation Steps:** +1. Remove "Create with Builder" button from Sites/List.tsx (line 717-720) +2. Remove "Blueprints" tab from tabItems (line 689) +3. Remove "Create Site" tab from tabItems (line 688) OR rename to "Add Site" +4. Remove or disable `/sites/builder` and `/sites/blueprints` routes in App.tsx +5. Update DeploymentPanel to not depend on Blueprints + +--- + +### 7. Linker & Optimizer Hidden from Sidebar + +**Status:** ❌ **NOT IMPLEMENTED** + +**Expected Implementation:** +- Hide Linker and Optimizer from main navigation +- Make accessible only through admin/settings + +**Current State:** +- ❌ Linker IS VISIBLE in sidebar (WORKFLOW section) +- ❌ Optimizer IS VISIBLE in sidebar (WORKFLOW section) +- βœ… Gated by module enable settings (can be disabled) + +**Code Evidence:** +```tsx +// frontend/src/layout/AppSidebar.tsx - Lines 136-153 +const workflowItems: NavItem[] = []; + +if (moduleEnabled('planner')) { + workflowItems.push({ + icon: , + name: "Planner", + path: "/planner/keywords", + }); +} + +if (moduleEnabled('writer')) { + workflowItems.push({ + icon: , + name: "Writer", + path: "/writer/content", + }); +} + +// ❌ LINKER STILL ADDED TO WORKFLOW +if (moduleEnabled('linker')) { + workflowItems.push({ + icon: , + name: "Linker", + path: "/linker/content", + }); +} + +// ❌ OPTIMIZER STILL ADDED TO WORKFLOW +if (moduleEnabled('optimizer')) { + workflowItems.push({ + icon: , + name: "Optimizer", + path: "/optimizer/content", + }); +} +``` + +**Affected Files:** +1. `frontend/src/layout/AppSidebar.tsx` - Lines 136-153 +2. Module enable settings control visibility (partial mitigation) + +**Recommendation:** πŸ”΄ **CRITICAL - REMOVE FROM SIDEBAR** + +**Implementation Steps:** +1. Remove Linker and Optimizer from `workflowItems` in AppSidebar.tsx +2. Move to admin-only section or remove entirely +3. OR update module enable settings to default Linker/Optimizer to disabled +4. Update documentation to reflect Linker/Optimizer as deprecated/future features + +--- + +### 8. Sites Page UX Cleanup + +**Status:** ❌ **NOT IMPLEMENTED** + +**Expected Implementation:** +- Remove "Pages" button from site cards +- Move "Sectors" button to Settings page +- Clean up top banners +- Simplify navigation + +**Current State:** +- ❌ Not verified (need to check grid card buttons) +- ⚠️ Table/Grid toggle exists (line 726) +- βœ… ModuleNavigationTabs exists (line 690) + +**Files to Check:** +1. `frontend/src/pages/Sites/List.tsx` - Grid card rendering section +2. Need to search for "Pages" button and "Sectors" button in grid cards + +**Recommendation:** 🟑 **NEEDS VERIFICATION** + +**Next Steps:** +1. Read `Sites/List.tsx` lines 800-1000 to check grid card rendering +2. Search for "Pages" button in card actions +3. Search for "Sectors" button/modal in card actions +4. Verify top banner content + +--- + +### 9. Site Settings 2x2 Grid Layout + +**Status:** ❌ **NOT IMPLEMENTED** + +**Expected Implementation:** +- Merge SEO tabs (Meta Tags, Open Graph, Schema) into single page with 2x2 grid +- Add Sectors tab to Settings +- Simplify tab structure + +**Current State:** +- ❌ SEO tabs STILL SEPARATE (seo, og, schema) +- ❌ NO Sectors tab in Settings +- βœ… WordPress integration tab exists +- βœ… Content Types tab exists + +**Code Evidence:** +```tsx +// frontend/src/pages/Sites/Settings.tsx - Lines 43-44 +const initialTab = (searchParams.get('tab') as + 'general' | 'seo' | 'og' | 'schema' | 'integrations' | 'content-types') || 'general'; + +const [activeTab, setActiveTab] = useState< + 'general' | 'seo' | 'og' | 'schema' | 'integrations' | 'content-types' +>(initialTab); + +// ❌ STILL HAS SEPARATE TABS: 'seo', 'og', 'schema' +// ❌ NO 'sectors' tab +``` + +**Current Tab Structure:** +1. βœ… `general` - Site name, domain, description +2. ❌ `seo` - Meta tags (should be merged) +3. ❌ `og` - Open Graph (should be merged) +4. ❌ `schema` - Schema.org (should be merged) +5. βœ… `integrations` - WordPress/Shopify integrations +6. βœ… `content-types` - Content type management +7. ❌ **MISSING:** `sectors` tab + +**Expected Tab Structure:** +1. `general` - Site name, domain, description +2. `seo-metadata` - 2x2 grid: Meta Tags | Open Graph | Schema.org | Twitter Cards +3. `sectors` - Manage active sectors for site +4. `integrations` - WordPress/Shopify integrations +5. `content-types` - Content type management + +**Recommendation:** πŸ”΄ **CRITICAL - REFACTOR SETTINGS TABS** + +**Implementation Steps:** +1. Merge `seo`, `og`, `schema` tabs into single `seo-metadata` tab +2. Create 2x2 grid layout component for SEO metadata +3. Add `sectors` tab with sector management UI +4. Update tab type definitions +5. Update URL param handling +6. Test all form submissions + +--- + +### 10. Site Card Button Rename + +**Status:** 🟑 **NEEDS VERIFICATION** + +**Expected Implementation:** +- "Content" button renamed to "Content Manager" +- "Pages" button removed +- "Sectors" button moved to Settings + +**Current State:** +- ⚠️ Need to check grid card rendering in Sites/List.tsx +- ⚠️ Need to verify button labels + +**Recommendation:** 🟑 **VERIFY GRID CARD BUTTONS** + +**Next Steps:** +1. Read Sites/List.tsx grid rendering section +2. Check for "Content" button label +3. Check for "Pages" and "Sectors" buttons + +--- + +### 11. Content Taxonomy Model + +**Status:** βœ… **FULLY IMPLEMENTED** + +**Implementation Details:** +- **File:** `backend/igny8_core/business/content/models.py` +- **Model:** `ContentTaxonomy` +- **Lines:** 296-398 + +**Features Implemented:** +1. βœ… `taxonomy_type` - category, tag, product_cat, product_tag, product_attr, service_cat +2. βœ… `sync_status` - native, imported, synced +3. βœ… `external_id`, `external_taxonomy` - WordPress integration fields +4. βœ… Hierarchical support via `parent` ForeignKey +5. βœ… Cluster mapping via `clusters` ManyToManyField +6. βœ… Unique constraints on slug and external_id + +**Code Evidence:** +```python +# backend/igny8_core/business/content/models.py +class ContentTaxonomy(SiteSectorBaseModel): + TAXONOMY_TYPE_CHOICES = [ + ('category', 'Category'), + ('tag', 'Tag'), + ('product_cat', 'Product Category'), + ('product_tag', 'Product Tag'), + ('product_attr', 'Product Attribute'), + ('service_cat', 'Service Category'), + ] + + SYNC_STATUS_CHOICES = [ + ('native', 'Native IGNY8'), + ('imported', 'Imported from External'), + ('synced', 'Synced with External'), + ] + + name = models.CharField(max_length=255, db_index=True) + slug = models.SlugField(max_length=255, db_index=True) + taxonomy_type = models.CharField(max_length=50, choices=TAXONOMY_TYPE_CHOICES, db_index=True) + parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='children') + + # WordPress/WooCommerce sync fields + external_id = models.IntegerField(null=True, blank=True, db_index=True) + external_taxonomy = models.CharField(max_length=100, blank=True) + sync_status = models.CharField(max_length=50, choices=SYNC_STATUS_CHOICES, default='native', db_index=True) + + # Cluster mapping + clusters = models.ManyToManyField('planner.Clusters', blank=True, related_name='taxonomy_terms') +``` + +**Verification:** +- βœ… Supports all required taxonomy types +- βœ… WordPress integration fields present +- βœ… Sync status tracking implemented +- βœ… Hierarchical taxonomy support +- βœ… Cluster mapping for semantic relationships + +**Recommendation:** βœ… **No changes needed** + +--- + +### 12. WordPress Content Import (Integration Only) + +**Status:** βœ… **FULLY IMPLEMENTED** (in IGNY8 backend + WP plugin) + +**Implementation Details:** + +**IGNY8 Backend:** +- **File:** `backend/igny8_core/modules/integration/` (various files) +- **Endpoints:** REST API endpoints for receiving WordPress data +- **Models:** `Content`, `ContentTaxonomy` with `sync_status` and `external_*` fields + +**WordPress Plugin:** +- **Files:** `igny8-wp-integration/includes/class-igny8-rest-api.php` +- **Endpoints:** `/wp-json/igny8/v1/site-metadata/`, `/post-by-task-id/`, `/post-by-content-id/` +- **Sync Logic:** `sync/igny8-to-wp.php`, `sync/post-sync.php` + +**Data Flow:** +1. βœ… WordPress β†’ IGNY8: Site structure discovery via `/site-metadata/` endpoint +2. βœ… WordPress β†’ IGNY8: Content import with postmeta `_igny8_task_id`, `_igny8_content_id` +3. βœ… IGNY8 β†’ WordPress: Publish content back to WP via REST API +4. βœ… Taxonomy sync: Categories, tags, product attributes + +**Fields for Import:** +- βœ… `sync_status` = 'imported' for WP content +- βœ… `source` = 'wordpress' +- βœ… `external_id` = WP post ID +- βœ… `external_url` = WP post URL +- βœ… `external_type` = WP post type (post, page, product) +- βœ… `sync_metadata` JSONField for additional WP data + +**Verification:** +- βœ… Content model supports import tracking +- βœ… WordPress plugin exposes required endpoints +- βœ… Sync status properly tracked + +**Recommendation:** βœ… **No changes needed** (WordPress integration working as designed) + +--- + +### 13. Module Enable Settings (Linker/Optimizer Default OFF) + +**Status:** 🟑 **PARTIALLY IMPLEMENTED** + +**Implementation Details:** +- **File:** `frontend/src/store/settingsStore.ts` +- **Backend:** `backend/igny8_core/modules/system/models.py` (likely `ModuleEnableSettings` model) + +**Current State:** +- βœ… Module enable settings exist and are checked in AppSidebar +- βœ… Linker and Optimizer are gated by `moduleEnabled()` checks +- ⚠️ **DEFAULT VALUES NOT VERIFIED** - need to check backend defaults + +**Code Evidence:** +```tsx +// frontend/src/layout/AppSidebar.tsx - Lines 136-153 +if (moduleEnabled('linker')) { + workflowItems.push({ + icon: , + name: "Linker", + path: "/linker/content", + }); +} + +if (moduleEnabled('optimizer')) { + workflowItems.push({ + icon: , + name: "Optimizer", + path: "/optimizer/content", + }); +} +``` + +**Missing Verification:** +- ⚠️ What are the DEFAULT values for `linker` and `optimizer` modules? +- ⚠️ Are they enabled or disabled by default for new accounts? + +**Recommendation:** 🟑 **VERIFY DEFAULT VALUES** + +**Next Steps:** +1. Check `backend/igny8_core/modules/system/models.py` for `ModuleEnableSettings` +2. Verify default values in database migrations or model definitions +3. Ensure Linker and Optimizer default to `enabled=False` +4. Update settings UI to show Linker/Optimizer as "Future Feature" or hide entirely + +--- + +### 14. Content Manager Page + +**Status:** 🟑 **NEEDS VERIFICATION** + +**Expected Implementation:** +- `/sites/:id/content` route exists +- Shows all content for a site (posts, pages, products, services) +- Filters by entity_type, sync_status, cluster + +**Current State:** +- βœ… Route exists: `/sites/:id/content` (App.tsx line 462) +- βœ… Component exists: `frontend/src/pages/Sites/Content.tsx` +- ⚠️ **NEED TO VERIFY:** Does it filter by entity_type? Does it show sync_status? + +**Code Evidence:** +```tsx +// frontend/src/App.tsx - Lines 462-466 + + + +} /> +``` + +**Recommendation:** 🟑 **VERIFY CONTENT MANAGER FEATURES** + +**Next Steps:** +1. Read `frontend/src/pages/Sites/Content.tsx` +2. Verify it shows content from ALL entity types (posts, pages, products, services) +3. Verify filters: entity_type, sync_status, cluster, status +4. Verify it displays imported content (sync_status='imported') + +--- + +### 15. Cluster-Driven Content Generation + +**Status:** βœ… **FULLY IMPLEMENTED** (Backend Models + Relationships) + +**Implementation Details:** + +**Models:** +1. βœ… `Clusters` model with `context_type` and `dimension_meta` +2. βœ… `Keywords.cluster` ForeignKey +3. βœ… `ContentIdeas.keyword_cluster` ForeignKey +4. βœ… `ContentIdeas.cluster_role` field +5. βœ… `Tasks.cluster` ForeignKey + `cluster_role` field +6. βœ… `Content.cluster` ForeignKey + `cluster_role` field + +**Relationships:** +``` +Clusters β†’ Keywords (1:M) +Clusters β†’ ContentIdeas (1:M) +Clusters β†’ Tasks (1:M) +Clusters β†’ Content (1:M) +``` + +**Code Evidence:** +```python +# backend/igny8_core/business/planning/models.py +class Keywords(SiteSectorBaseModel): + cluster = models.ForeignKey( + 'Clusters', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='keywords' + ) + +# backend/igny8_core/business/content/models.py +class Tasks(SiteSectorBaseModel): + cluster = models.ForeignKey( + 'planner.Clusters', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='tasks' + ) + cluster_role = models.CharField(max_length=50, choices=CLUSTER_ROLE_CHOICES, default='hub') + +class Content(SiteSectorBaseModel): + cluster = models.ForeignKey( + 'planner.Clusters', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='contents' + ) + cluster_role = models.CharField(max_length=50, choices=CLUSTER_ROLE_CHOICES, default='supporting') +``` + +**Verification:** +- βœ… All models have cluster relationships +- βœ… `cluster_role` field exists on Tasks and Content +- βœ… Cluster detail page can list all related Keywords, Ideas, Tasks, Content +- ⚠️ **MISSING UI:** No cluster detail page to visualize relationships + +**Recommendation:** 🟑 **BACKEND COMPLETE, NEED FRONTEND UI** + +**Next Steps:** +1. Create Cluster Detail page (see Feature #4) +2. Show cluster hierarchy visualization +3. Show keywordβ†’ideaβ†’taskβ†’content generation flow + +--- + +## Summary Table + +| # | Feature | Status | Files Affected | Priority | +|---|---------|--------|----------------|----------| +| 1 | Persistent Login | βœ… DONE | `authStore.ts` | βœ… Complete | +| 2 | Writer Task Fields | βœ… DONE | `content/models.py` | βœ… Complete | +| 3 | Content Model Fields | βœ… DONE | `content/models.py` | βœ… Complete | +| 4 | Cluster Detail Page | ❌ MISSING | `App.tsx`, `ClusterDetail.tsx` (new) | πŸ”΄ CRITICAL | +| 5 | Cluster Model Fields | βœ… DONE | `planning/models.py` | βœ… Complete | +| 6 | Site Builder Hidden | ❌ NOT DONE | `Sites/List.tsx` | πŸ”΄ CRITICAL | +| 7 | Linker/Optimizer Hidden | ❌ NOT DONE | `AppSidebar.tsx` | πŸ”΄ CRITICAL | +| 8 | Sites Page UX Cleanup | 🟑 VERIFY | `Sites/List.tsx` | 🟑 Medium | +| 9 | Settings 2x2 Grid | ❌ NOT DONE | `Sites/Settings.tsx` | πŸ”΄ CRITICAL | +| 10 | Site Card Button Rename | 🟑 VERIFY | `Sites/List.tsx` | 🟑 Medium | +| 11 | Content Taxonomy Model | βœ… DONE | `content/models.py` | βœ… Complete | +| 12 | WordPress Import | βœ… DONE | Backend + WP Plugin | βœ… Complete | +| 13 | Module Enable Defaults | 🟑 VERIFY | Backend models | 🟑 Medium | +| 14 | Content Manager | 🟑 VERIFY | `Sites/Content.tsx` | 🟑 Medium | +| 15 | Cluster-Driven Content | βœ… DONE | Backend models | ⚠️ Need UI | + +--- + +## Priority Fixes + +### πŸ”΄ CRITICAL (Must Fix Before Launch) + +1. **Create Cluster Detail Page** (Feature #4) + - Route: `/planner/clusters/:id` + - Component: `ClusterDetail.tsx` + - API: `/v1/planner/clusters/:id/keywords/`, `/ideas/`, `/tasks/` + +2. **Remove Site Builder UI** (Feature #6) + - Remove "Create with Builder" button + - Remove "Blueprints" navigation tab + - Disable Builder routes + +3. **Hide Linker & Optimizer** (Feature #7) + - Remove from AppSidebar WORKFLOW section + - OR set default enabled=False in module settings + +4. **Refactor Settings Tabs** (Feature #9) + - Merge SEO tabs into 2x2 grid + - Add Sectors tab + - Simplify navigation + +### 🟑 MEDIUM (Verify & Fix) + +5. **Verify Sites Page UX** (Feature #8) + - Check grid card buttons + - Confirm "Pages" and "Sectors" buttons removed + +6. **Verify Content Manager** (Feature #14) + - Check entity_type filters + - Check sync_status display + +7. **Verify Module Defaults** (Feature #13) + - Check Linker/Optimizer default values + +--- + +## WordPress Plugin Integration Notes + +The WordPress plugin (`igny8-wp-integration/`) is **working correctly** for content import/sync and is **excluded from this audit** except where it integrates with IGNY8: + +- βœ… REST endpoints for site metadata discovery +- βœ… Postmeta storage (`_igny8_task_id`, `_igny8_content_id`) +- βœ… Taxonomy sync (categories, tags, product attributes) +- βœ… Two-way sync: WP ↔ IGNY8 + +**No changes needed** to WordPress plugin based on refactor plan. + +--- + +## Recommendations + +### Immediate Actions (Week 1) + +1. **Create Cluster Detail Page** + - Create component `ClusterDetail.tsx` + - Add route `/planner/clusters/:id` + - Make cluster names clickable in table + - Show keywords, ideas, tasks, content + +2. **Remove Builder UI** + - Remove buttons from Sites/List.tsx + - Remove Blueprints tab + - Disable routes + +3. **Hide Linker/Optimizer** + - Remove from sidebar or set default disabled + - Update documentation + +### Short-term Actions (Week 2-3) + +4. **Refactor Settings Tabs** + - Create 2x2 grid component for SEO + - Merge tabs + - Add Sectors tab + +5. **Verify & Fix Sites UX** + - Check grid cards + - Rename buttons + - Test navigation + +### Long-term Actions (Month 1-2) + +6. **Complete Content Manager** + - Add entity_type filters + - Add sync_status badges + - Add cluster filtering + +7. **Update Documentation** + - Update user guides for new UI + - Document cluster workflow + - Document content import flow + +--- + +## Conclusion + +The IGNY8 app has **strong backend foundation** (βœ… 5/15 features complete) but **critical UI/UX gaps** (❌ 4/15 features missing, 🟑 3/15 need verification). + +**Top Priority:** Create Cluster Detail Page, remove Builder UI, hide Linker/Optimizer, refactor Settings tabs. + +**Estimated Effort:** ~2-3 weeks for critical fixes, 1-2 weeks for verification/polish. + +--- + +**Report Generated:** December 2024 +**Next Review:** After critical fixes implemented diff --git a/planner-writer-workflow.md b/planner-writer-workflow.md new file mode 100644 index 00000000..61e9557c --- /dev/null +++ b/planner-writer-workflow.md @@ -0,0 +1,1466 @@ +# IGNY8 Planner β†’ Writer Workflow Documentation +**Complete Technical Implementation Guide** +**Date:** November 24, 2025 +**Version:** 1.0 + +--- + +## Table of Contents +1. [Overview](#overview) +2. [Database Tables & Models](#database-tables--models) +3. [Frontend Pages & Components](#frontend-pages--components) +4. [Backend API Endpoints](#backend-api-endpoints) +5. [Complete Workflow](#complete-workflow) +6. [AI Functions](#ai-functions) +7. [Flowchart Diagrams](#flowchart-diagrams) + +--- + +## Overview + +The IGNY8 content workflow consists of two primary modules: +- **Planner Module**: Keyword research, clustering, and content ideation +- **Writer Module**: Task management, content generation, and publishing + +Data flows sequentially through these stages: +``` +Keywords β†’ Clusters β†’ Ideas β†’ Tasks β†’ Content β†’ Images +``` + +--- + +## Database Tables & Models + +### Planner Module Tables + +#### 1. `igny8_keywords` (Keywords Model) +**Location:** `backend/igny8_core/business/planning/models.py` + +| Field | Type | Description | Choices | +|-------|------|-------------|---------| +| `id` | Integer | Primary key | - | +| `seed_keyword_id` | ForeignKey | Reference to global SeedKeyword | - | +| `volume_override` | Integer | Site-specific volume override | - | +| `difficulty_override` | Integer | Site-specific difficulty override | - | +| `cluster_id` | ForeignKey | Parent cluster | Clusters | +| `status` | CharField(50) | Keyword status | active, pending, archived | +| `account` | ForeignKey | Owner account | - | +| `site` | ForeignKey | Parent site | - | +| `sector` | ForeignKey | Parent sector | - | +| `created_at` | DateTime | Creation timestamp | - | +| `updated_at` | DateTime | Last update timestamp | - | + +**Indexes:** +- `seed_keyword` +- `status` +- `cluster` +- `site, sector` +- `seed_keyword, site, sector` (unique) + +**Properties (Computed from SeedKeyword):** +- `keyword` - Text from seed_keyword.keyword +- `volume` - volume_override or seed_keyword.volume +- `difficulty` - difficulty_override or seed_keyword.difficulty +- `intent` - seed_keyword.intent + +--- + +#### 2. `igny8_clusters` (Clusters Model) +**Location:** `backend/igny8_core/business/planning/models.py` + +| Field | Type | Description | Choices | +|-------|------|-------------|---------| +| `id` | Integer | Primary key | - | +| `name` | CharField(255) | Cluster name (unique) | - | +| `description` | TextField | Cluster description | - | +| `keywords_count` | Integer | Number of keywords | - | +| `volume` | Integer | Total search volume | - | +| `mapped_pages` | Integer | Number of mapped pages | - | +| `status` | CharField(50) | Cluster status | active, archived | +| `context_type` | CharField(50) | Cluster dimension | topic, attribute, service_line | +| `dimension_meta` | JSONField | Extended metadata | - | +| `account` | ForeignKey | Owner account | - | +| `site` | ForeignKey | Parent site | - | +| `sector` | ForeignKey | Parent sector | - | +| `created_at` | DateTime | Creation timestamp | - | +| `updated_at` | DateTime | Last update timestamp | - | + +**Indexes:** +- `name` +- `status` +- `site, sector` +- `context_type` + +**Relationships:** +- `keywords` (1:M) - Related Keywords +- `ideas` (1:M) - Related ContentIdeas +- `tasks` (1:M) - Related Tasks +- `contents` (1:M) - Related Content + +--- + +#### 3. `igny8_content_ideas` (ContentIdeas Model) +**Location:** `backend/igny8_core/business/planning/models.py` + +| Field | Type | Description | Choices | +|-------|------|-------------|---------| +| `id` | Integer | Primary key | - | +| `idea_title` | CharField(255) | Idea title | - | +| `description` | TextField | Idea description | - | +| `target_keywords` | CharField(500) | Comma-separated keywords (legacy) | - | +| `keyword_objects` | ManyToManyField | Keywords linked to idea | Keywords | +| `keyword_cluster_id` | ForeignKey | Parent cluster | Clusters | +| `taxonomy_id` | ForeignKey | Optional taxonomy association | SiteBlueprintTaxonomy | +| `status` | CharField(50) | Idea status | new, scheduled, published | +| `estimated_word_count` | Integer | Target word count | - | +| `site_entity_type` | CharField(50) | Target entity type | post, page, product, service, taxonomy_term | +| `cluster_role` | CharField(50) | Role within cluster | hub, supporting, attribute | +| `account` | ForeignKey | Owner account | - | +| `site` | ForeignKey | Parent site | - | +| `sector` | ForeignKey | Parent sector | - | +| `created_at` | DateTime | Creation timestamp | - | +| `updated_at` | DateTime | Last update timestamp | - | + +**Indexes:** +- `idea_title` +- `status` +- `keyword_cluster` +- `site_entity_type` +- `cluster_role` +- `site, sector` + +--- + +### Writer Module Tables + +#### 4. `igny8_tasks` (Tasks Model) +**Location:** `backend/igny8_core/business/content/models.py` + +| Field | Type | Description | Choices | +|-------|------|-------------|---------| +| `id` | Integer | Primary key | - | +| `title` | CharField(255) | Task title | - | +| `description` | TextField | Task description | - | +| `keywords` | CharField(500) | Comma-separated keywords (legacy) | - | +| `cluster_id` | ForeignKey | Parent cluster | Clusters | +| `keyword_objects` | ManyToManyField | Keywords linked to task | Keywords | +| `idea_id` | ForeignKey | Source idea | ContentIdeas | +| `status` | CharField(50) | Task status | queued, in_progress, completed, failed | +| `entity_type` | CharField(50) | Content entity type | post, page, product, service, taxonomy_term | +| `taxonomy_id` | ForeignKey | Taxonomy association | SiteBlueprintTaxonomy | +| `cluster_role` | CharField(50) | Role within cluster | hub, supporting, attribute | +| `account` | ForeignKey | Owner account | - | +| `site` | ForeignKey | Parent site | - | +| `sector` | ForeignKey | Parent sector | - | +| `created_at` | DateTime | Creation timestamp | - | +| `updated_at` | DateTime | Last update timestamp | - | + +**Indexes:** +- `title` +- `status` +- `cluster` +- `entity_type` +- `cluster_role` +- `site, sector` + +**Relationships:** +- `content_record` (1:1) - Related Content (OneToOneField) + +--- + +#### 5. `igny8_content` (Content Model) +**Location:** `backend/igny8_core/business/content/models.py` + +| Field | Type | Description | Choices | +|-------|------|-------------|---------| +| `id` | Integer | Primary key | - | +| `task_id` | OneToOneField | Parent task | Tasks | +| `html_content` | TextField | Final HTML content | - | +| `word_count` | Integer | Content word count | - | +| `metadata` | JSONField | Additional metadata | - | +| `title` | CharField(255) | Content title | - | +| `meta_title` | CharField(255) | SEO meta title | - | +| `meta_description` | TextField | SEO meta description | - | +| `primary_keyword` | CharField(255) | Primary keyword | - | +| `secondary_keywords` | JSONField | List of secondary keywords | - | +| `status` | CharField(50) | Content workflow status | draft, review, publish | +| `source` | CharField(50) | Content source | igny8, wordpress, shopify, custom | +| `sync_status` | CharField(50) | Sync status | native, imported, synced | +| `external_id` | CharField(255) | External platform ID | - | +| `external_url` | URLField | External platform URL | - | +| `external_type` | CharField(100) | External post type | - | +| `sync_metadata` | JSONField | Platform-specific sync metadata | - | +| `internal_links` | JSONField | Internal links (linker) | - | +| `linker_version` | Integer | Linker processing version | - | +| `optimizer_version` | Integer | Optimizer processing version | - | +| `optimization_scores` | JSONField | Optimization scores | - | +| `entity_type` | CharField(50) | Content entity type | post, page, product, service, taxonomy_term | +| `content_format` | CharField(50) | Content format (posts only) | article, listicle, guide, comparison, review, roundup | +| `cluster_role` | CharField(50) | Role within cluster | hub, supporting, attribute | +| `json_blocks` | JSONField | Structured content blocks | - | +| `structure_data` | JSONField | Content structure data | - | +| `taxonomies` | ManyToManyField | Associated taxonomy terms | ContentTaxonomy | +| `cluster_id` | ForeignKey | Primary semantic cluster | Clusters | +| `account` | ForeignKey | Owner account | - | +| `site` | ForeignKey | Parent site | - | +| `sector` | ForeignKey | Parent sector | - | +| `generated_at` | DateTime | Generation timestamp | - | +| `updated_at` | DateTime | Last update timestamp | - | + +**Indexes:** +- `task` +- `generated_at` +- `source` +- `sync_status` +- `source, sync_status` +- `entity_type` +- `content_format` +- `cluster_role` +- `cluster` +- `external_type` +- `site, entity_type` + +--- + +#### 6. `igny8_images` (Images Model) +**Location:** `backend/igny8_core/business/content/models.py` + +| Field | Type | Description | Choices | +|-------|------|-------------|---------| +| `id` | Integer | Primary key | - | +| `content_id` | ForeignKey | Parent content | Content | +| `image_type` | CharField(50) | Image type | featured, in_article | +| `position` | Integer | Position in article | - | +| `prompt` | TextField | Image generation prompt | - | +| `image_url` | URLField | Generated image URL | - | +| `status` | CharField(50) | Image status | pending, generated, failed | +| `provider` | CharField(50) | Image generation provider | dall-e, stable-diffusion, midjourney | +| `model` | CharField(100) | Image generation model | - | +| `error_message` | TextField | Error message (if failed) | - | +| `metadata` | JSONField | Additional metadata | - | +| `account` | ForeignKey | Owner account | - | +| `site` | ForeignKey | Parent site | - | +| `sector` | ForeignKey | Parent sector | - | +| `created_at` | DateTime | Creation timestamp | - | +| `updated_at` | DateTime | Last update timestamp | - | + +**Indexes:** +- `content` +- `image_type` +- `status` +- `site, sector` + +--- + +## Frontend Pages & Components + +### Planner Module Pages + +#### 1. Keywords Page (`/planner/keywords`) +**Component:** `frontend/src/pages/Planner/Keywords.tsx` + +**Key Functions:** +- `loadKeywords()` - Fetch keywords from API with filters/pagination +- `handleCreateSeedKeyword()` - Attach SeedKeyword to site/sector +- `handleEdit(keyword)` - Edit keyword (override volume/difficulty, assign cluster) +- `handleBulkAction('auto_cluster', ids)` - AI clustering (max 20 keywords) +- `handleBulkDelete(ids)` - Delete keywords +- `handleBulkUpdateStatus(ids, status)` - Update status +- `handleExport()` - Export keywords to CSV +- `handleImportClick()` - Import keywords from CSV + +**API Calls:** +```typescript +fetchKeywords(filters: KeywordFilters): Promise> +createKeyword(data: KeywordCreateData): Promise +updateKeyword(id: number, data: KeywordCreateData): Promise +deleteKeyword(id: number): Promise +bulkDeleteKeywords(ids: number[]): Promise<{ deleted_count: number }> +bulkUpdateKeywordsStatus(ids: number[], status: string): Promise<{ updated_count: number }> +autoClusterKeywords(ids: number[], sectorId?: number): Promise +``` + +**State Management:** +- `keywords` - Keyword list +- `clusters` - Cluster dropdown options +- `availableSeedKeywords` - Available SeedKeywords for attachment +- `searchTerm`, `statusFilter`, `clusterFilter`, `intentFilter`, `difficultyFilter`, `volumeMin`, `volumeMax` - Filters +- `currentPage`, `totalPages`, `totalCount` - Pagination +- `sortBy`, `sortDirection` - Sorting +- `selectedIds` - Bulk selection + +**AI Function Logs:** +When Resource Debug is enabled, logs all AI function calls: +- Request: keyword_ids, sector_id +- Success: task_id (async) or clusters_created, keywords_updated (sync) +- Progress steps: phase, percentage, message +- Errors: error message + +--- + +#### 2. Clusters Page (`/planner/clusters`) +**Component:** `frontend/src/pages/Planner/Clusters.tsx` + +**Key Functions:** +- `loadClusters()` - Fetch clusters from API +- `handleCreateCluster()` - Create new cluster manually +- `handleEdit(cluster)` - Edit cluster name/description +- `handleRowAction('generate_ideas', cluster)` - Generate ideas for single cluster +- `handleBulkAction('auto_generate_ideas', ids)` - Generate ideas for multiple clusters (max 5) +- `handleBulkDelete(ids)` - Delete clusters +- `handleBulkUpdateStatus(ids, status)` - Update status + +**API Calls:** +```typescript +fetchClusters(filters: ClusterFilters): Promise> +createCluster(data: ClusterCreateData): Promise +updateCluster(id: number, data: ClusterCreateData): Promise +deleteCluster(id: number): Promise +bulkDeleteClusters(ids: number[]): Promise<{ deleted_count: number }> +bulkUpdateClustersStatus(ids: number[], status: string): Promise<{ updated_count: number }> +autoGenerateIdeas(clusterIds: number[]): Promise +``` + +**State Management:** +- `clusters` - Cluster list +- `searchTerm`, `statusFilter`, `difficultyFilter`, `volumeMin`, `volumeMax` - Filters +- `currentPage`, `totalPages`, `totalCount` - Pagination +- `sortBy`, `sortDirection` - Sorting +- `selectedIds` - Bulk selection + +**AI Progress Modal:** +Shows progress for `auto_generate_ideas` async tasks: +- Title: "Generating Content Ideas" +- Function ID: `ai-generate-ideas-01-desktop` +- Displays: phase, percentage, message, step logs + +--- + +#### 3. Ideas Page (`/planner/ideas`) +**Component:** `frontend/src/pages/Planner/Ideas.tsx` + +**Key Functions:** +- `loadIdeas()` - Fetch content ideas from API +- `handleCreateIdea()` - Create new idea manually +- `handleEdit(idea)` - Edit idea title/description/cluster +- `handleRowAction('queue_to_writer', idea)` - Convert single idea to task +- `handleBulkAction('queue_to_writer', ids)` - Convert multiple ideas to tasks +- `handleBulkDelete(ids)` - Delete ideas +- `handleBulkUpdateStatus(ids, status)` - Update status + +**API Calls:** +```typescript +fetchContentIdeas(filters: ContentIdeasFilters): Promise> +createContentIdea(data: ContentIdeaCreateData): Promise +updateContentIdea(id: number, data: ContentIdeaCreateData): Promise +deleteContentIdea(id: number): Promise +bulkDeleteContentIdeas(ids: number[]): Promise<{ deleted_count: number }> +bulkUpdateContentIdeasStatus(ids: number[], status: string): Promise<{ updated_count: number }> +bulkQueueIdeasToWriter(ids: number[]): Promise<{ created_count: number }> +``` + +**State Management:** +- `ideas` - Idea list +- `clusters` - Cluster dropdown options +- `searchTerm`, `statusFilter`, `clusterFilter`, `structureFilter`, `typeFilter`, `entityTypeFilter` - Filters +- `currentPage`, `totalPages`, `totalCount` - Pagination +- `sortBy`, `sortDirection` - Sorting +- `selectedIds` - Bulk selection + +**Queue to Writer Logic:** +Only ideas with `status='new'` can be queued. Creates Tasks with: +- `title` = `idea_title` +- `description` = `idea.description` +- `keywords` = `idea.target_keywords` +- `cluster` = `idea.keyword_cluster` +- `idea` = `idea.id` +- `status` = 'queued' +- `entity_type` = `idea.site_entity_type` +- `cluster_role` = `idea.cluster_role` +- `taxonomy` = `idea.taxonomy` + +--- + +### Writer Module Pages + +#### 4. Tasks Page (`/writer/tasks`) +**Component:** `frontend/src/pages/Writer/Tasks.tsx` + +**Key Functions:** +- `loadTasks()` - Fetch tasks from API +- `handleCreateTask()` - Create new task manually +- `handleEdit(task)` - Edit task title/description/cluster +- `handleRowAction('generate_content', task)` - Generate content for single task (AI) +- `handleBulkAction('generate_images', ids)` - Generate images for multiple tasks (max 10) +- `handleBulkDelete(ids)` - Delete tasks +- `handleBulkUpdateStatus(ids, status)` - Update status + +**API Calls:** +```typescript +fetchTasks(filters: TasksFilters): Promise> +createTask(data: TaskCreateData): Promise +updateTask(id: number, data: TaskCreateData): Promise +deleteTask(id: number): Promise +bulkDeleteTasks(ids: number[]): Promise<{ deleted_count: number }> +bulkUpdateTasksStatus(ids: number[], status: string): Promise<{ updated_count: number }> +autoGenerateContent(taskIds: number[]): Promise +autoGenerateImages(taskIds: number[]): Promise +``` + +**State Management:** +- `tasks` - Task list +- `clusters` - Cluster dropdown options +- `searchTerm`, `statusFilter`, `clusterFilter`, `structureFilter`, `typeFilter`, `sourceFilter`, `entityTypeFilter` - Filters +- `currentPage`, `totalPages`, `totalCount` - Pagination +- `sortBy`, `sortDirection` - Sorting +- `selectedIds` - Bulk selection +- `aiLogs` - AI function logs (when Resource Debug enabled) + +**AI Function Logs:** +Logs all AI function calls: +- Request: task_ids, task_title +- Success: task_id (async) or tasks_updated, images_created (sync) +- Progress steps: phase, percentage, message +- Errors: error message + +**AI Progress Modal:** +Shows progress for `auto_generate_content` async tasks: +- Title: "Generating Content" +- Function ID: `ai-generate-content-03` +- Displays: phase, percentage, message, step logs + +--- + +#### 5. Content Page (`/writer/content`) +**Component:** `frontend/src/pages/Writer/Content.tsx` + +**Key Functions:** +- `loadContent()` - Fetch content from API +- `handleRowAction('generate_image_prompts', content)` - Generate AI image prompts +- `handleRowAction('optimize', content)` - Run optimizer on content +- `handleRowAction('send_to_optimizer', content)` - Navigate to optimizer page + +**API Calls:** +```typescript +fetchContent(filters: ContentFilters): Promise> +generateImagePrompts(contentIds: number[]): Promise +optimizerApi.optimize(contentId: number, source: string): Promise +``` + +**State Management:** +- `content` - Content list +- `searchTerm`, `statusFilter`, `sourceFilter`, `syncStatusFilter` - Filters +- `currentPage`, `totalPages`, `totalCount` - Pagination +- `sortBy`, `sortDirection` - Sorting +- `selectedIds` - Bulk selection + +**Content Row Actions:** +1. **View** - Navigate to `/writer/content/:id` (ContentView page) +2. **Generate Image Prompts** - AI function to create smart image prompts +3. **Optimize** - Run optimizer to improve SEO scores +4. **Send to Optimizer** - Navigate to `/optimizer/content?contentId=:id` + +--- + +#### 6. Images Page (`/writer/images`) +**Component:** `frontend/src/pages/Writer/Images.tsx` + +**Key Functions:** +- `loadImages()` - Fetch content images grouped by content +- `handleRowAction('update_status', imageGroup)` - Update all images for content +- `handleGenerateImages(contentId)` - Generate images using AI (opens ImageQueueModal) +- `handleBulkExport(ids)` - Export image metadata + +**API Calls:** +```typescript +fetchContentImages(filters: {}): Promise +fetchImageGenerationSettings(): Promise +generateImages(contentId: number, imageType: string, prompt: string, provider: string, model: string): Promise<{ image_url: string }> +bulkUpdateImagesStatus(contentId: number, status: string): Promise<{ updated_count: number }> +``` + +**State Management:** +- `images` - ContentImagesGroup list (one row per content) +- `searchTerm`, `statusFilter` - Filters +- `currentPage`, `totalPages`, `totalCount` - Client-side pagination +- `sortBy`, `sortDirection` - Client-side sorting +- `selectedIds` - Bulk selection +- `imageQueue` - Image generation queue items +- `taskId`, `imageModel`, `imageProvider` - Image generation settings + +**ImageQueueModal:** +Displays real-time image generation progress: +- Featured image (always first) +- In-article images (up to max_in_article_images from settings) +- Shows: index, label, type, prompt, status, progress, image preview +- Updates via polling or WebSocket + +**Content Images Group Structure:** +```typescript +{ + content_id: number; + content_title: string; + featured_image: { id, prompt, image_url, status, provider, model }; + in_article_images: [{ id, position, prompt, image_url, status, provider, model }]; + overall_status: 'pending' | 'partial' | 'complete' | 'failed'; + total_images: number; + generated_images: number; +} +``` + +--- + +## Backend API Endpoints + +### Planner Module Endpoints + +#### Keywords API +**Base URL:** `/v1/planner/keywords/` + +| Endpoint | Method | Function | Description | +|----------|--------|----------|-------------| +| `/` | GET | `list()` | List keywords with filters/pagination | +| `/` | POST | `create()` | Create new keyword (attach SeedKeyword) | +| `/:id/` | GET | `retrieve()` | Get single keyword details | +| `/:id/` | PUT/PATCH | `update()` | Update keyword | +| `/:id/` | DELETE | `destroy()` | Delete keyword | +| `/bulk_delete/` | POST | `bulk_delete()` | Delete multiple keywords | +| `/bulk_update_status/` | POST | `bulk_update_status()` | Update status for multiple keywords | +| `/auto_cluster/` | POST | `auto_cluster()` | AI clustering function | +| `/export/` | GET | `export()` | Export keywords to CSV | +| `/import/` | POST | `import()` | Import keywords from CSV | + +**ViewSet:** `KeywordsViewSet` in `backend/igny8_core/modules/planner/views.py` + +--- + +#### Clusters API +**Base URL:** `/v1/planner/clusters/` + +| Endpoint | Method | Function | Description | +|----------|--------|----------|-------------| +| `/` | GET | `list()` | List clusters with filters/pagination | +| `/` | POST | `create()` | Create new cluster | +| `/:id/` | GET | `retrieve()` | Get single cluster details | +| `/:id/` | PUT/PATCH | `update()` | Update cluster | +| `/:id/` | DELETE | `destroy()` | Delete cluster | +| `/bulk_delete/` | POST | `bulk_delete()` | Delete multiple clusters | +| `/bulk_update_status/` | POST | `bulk_update_status()` | Update status for multiple clusters | +| `/auto_generate_ideas/` | POST | `auto_generate_ideas()` | AI idea generation function | + +**ViewSet:** `ClusterViewSet` in `backend/igny8_core/modules/planner/views.py` + +--- + +#### Content Ideas API +**Base URL:** `/v1/planner/content-ideas/` + +| Endpoint | Method | Function | Description | +|----------|--------|----------|-------------| +| `/` | GET | `list()` | List ideas with filters/pagination | +| `/` | POST | `create()` | Create new idea | +| `/:id/` | GET | `retrieve()` | Get single idea details | +| `/:id/` | PUT/PATCH | `update()` | Update idea | +| `/:id/` | DELETE | `destroy()` | Delete idea | +| `/bulk_delete/` | POST | `bulk_delete()` | Delete multiple ideas | +| `/bulk_update_status/` | POST | `bulk_update_status()` | Update status for multiple ideas | +| `/bulk_queue_to_writer/` | POST | `bulk_queue_to_writer()` | Convert ideas to tasks | + +**ViewSet:** `ContentIdeasViewSet` in `backend/igny8_core/modules/planner/views.py` + +--- + +### Writer Module Endpoints + +#### Tasks API +**Base URL:** `/v1/writer/tasks/` + +| Endpoint | Method | Function | Description | +|----------|--------|----------|-------------| +| `/` | GET | `list()` | List tasks with filters/pagination | +| `/` | POST | `create()` | Create new task | +| `/:id/` | GET | `retrieve()` | Get single task details | +| `/:id/` | PUT/PATCH | `update()` | Update task | +| `/:id/` | DELETE | `destroy()` | Delete task | +| `/bulk_delete/` | POST | `bulk_delete()` | Delete multiple tasks | +| `/bulk_update_status/` | POST | `bulk_update_status()` | Update status for multiple tasks | +| `/auto_generate_content/` | POST | `auto_generate_content()` | AI content generation function | + +**ViewSet:** `TasksViewSet` in `backend/igny8_core/modules/writer/views.py` + +--- + +#### Content API +**Base URL:** `/v1/writer/content/` + +| Endpoint | Method | Function | Description | +|----------|--------|----------|-------------| +| `/` | GET | `list()` | List content with filters/pagination | +| `/:id/` | GET | `retrieve()` | Get single content details | +| `/:id/` | PUT/PATCH | `update()` | Update content | +| `/:id/` | DELETE | `destroy()` | Delete content | +| `/generate_image_prompts/` | POST | `generate_image_prompts()` | AI image prompt generation | + +**ViewSet:** `ContentViewSet` in `backend/igny8_core/modules/writer/views.py` + +--- + +#### Images API +**Base URL:** `/v1/writer/images/` + +| Endpoint | Method | Function | Description | +|----------|--------|----------|-------------| +| `/` | GET | `list()` | List images with filters/pagination | +| `/content/` | GET | `by_content()` | List images grouped by content | +| `/settings/` | GET | `get_settings()` | Get image generation settings | +| `/auto_generate/` | POST | `auto_generate_images()` | AI bulk image generation (deprecated) | +| `/generate_images/` | POST | `generate_images()` | Generate images for content | +| `/bulk_update_status/` | POST | `bulk_update_status()` | Update status for content images | + +**ViewSet:** `ImagesViewSet` in `backend/igny8_core/modules/writer/views.py` + +--- + +## Complete Workflow + +### Stage 1: Keyword Research & Clustering + +#### Step 1.1: Attach SeedKeywords to Site +**Frontend:** Keywords Page β†’ "Add Keyword" button +**Function:** `handleCreateSeedKeyword()` +**API Call:** `POST /v1/planner/keywords/` + +**Request Payload:** +```json +{ + "seed_keyword_id": 123, + "site_id": 1, + "sector_id": 2, + "volume_override": null, + "difficulty_override": null, + "cluster_id": null, + "status": "pending" +} +``` + +**Backend Process:** +1. Validate `seed_keyword_id` exists in `seed_keywords` table +2. Validate `site_id` and `sector_id` exist and match +3. Check unique constraint: `seed_keyword + site + sector` +4. Create `Keywords` record with account/site/sector +5. Return created keyword with computed properties + +**Response:** +```json +{ + "success": true, + "data": { + "id": 456, + "seed_keyword_id": 123, + "keyword": "best coffee beans", + "volume": 12000, + "difficulty": 45, + "intent": "commercial", + "cluster_id": null, + "status": "pending", + "created_at": "2025-11-24T10:30:00Z" + }, + "message": "Keyword attached successfully" +} +``` + +--- + +#### Step 1.2: AI Auto-Clustering +**Frontend:** Keywords Page β†’ Select keywords β†’ Bulk Actions β†’ "Auto-Cluster" +**Function:** `handleBulkAction('auto_cluster', ids)` +**API Call:** `POST /v1/planner/keywords/auto_cluster/` + +**Request Payload:** +```json +{ + "ids": [456, 457, 458, 459, 460], + "sector_id": 2 +} +``` + +**Backend Process (`auto_cluster()` view):** +```python +# backend/igny8_core/modules/planner/views.py line 573 +def auto_cluster(self, request): + keyword_ids = request.data.get('ids', []) + sector_id = request.data.get('sector_id') + account = getattr(request, 'account', None) + + # Use ClusteringService + service = ClusteringService() + result = service.cluster_keywords(keyword_ids, account, sector_id) + + if result.get('success'): + if 'task_id' in result: + # Async task queued + return success_response(data={'task_id': result['task_id']}, message='Clustering started') + else: + # Synchronous execution + return success_response(data=result) +``` + +**ClusteringService Logic:** +**Location:** `backend/igny8_core/business/planning/services/clustering_service.py` + +1. **Load Keywords:** + - Fetch `Keywords` by IDs + - Get keyword text, volume, difficulty, intent from seed_keyword + +2. **Check Credits:** + - Calculate credits needed (based on keyword count) + - Deduct credits from account + +3. **AI Clustering:** + - Send keywords to AI engine + - Prompt: "Group these keywords into semantic clusters..." + - AI returns: `{ clusters: [{ name, keywords: [ids] }] }` + +4. **Create/Update Clusters:** + - For each AI cluster: + - Check if cluster with same name exists + - If exists, use existing; else create new `Clusters` record + - Update `Keywords.cluster_id` for all keywords in group + - Calculate `keywords_count`, `volume`, `difficulty` for cluster + +5. **Return Result:** + - Sync: `{ success: true, clusters_created: 3, keywords_updated: 20 }` + - Async: `{ success: true, task_id: 'uuid', message: 'Clustering started' }` + +**Response (Async):** +```json +{ + "success": true, + "data": { + "task_id": "abc123-def456-ghi789" + }, + "message": "Clustering started" +} +``` + +**Frontend Progress Tracking:** +```typescript +// Opens ProgressModal +progressModal.openModal(result.task_id, 'Auto-Clustering Keywords', 'ai-auto-cluster-01'); + +// ProgressModal polls /v1/system/task-progress/:task_id/ every 2 seconds +// Shows: phase, percentage, message, step logs +``` + +**AI Task Progress Phases:** +1. **Initializing** (0-10%) - Validating keywords, loading data +2. **Processing** (10-40%) - Sending to AI engine +3. **AI Analysis** (40-80%) - AI clustering keywords +4. **Saving Results** (80-95%) - Creating clusters, updating keywords +5. **Completed** (100%) - Done + +--- + +### Stage 2: Content Idea Generation + +#### Step 2.1: AI Generate Ideas from Clusters +**Frontend:** Clusters Page β†’ Select clusters β†’ Bulk Actions β†’ "Generate Ideas" +**Function:** `handleBulkAction('auto_generate_ideas', ids)` +**API Call:** `POST /v1/planner/clusters/auto_generate_ideas/` + +**Request Payload:** +```json +{ + "ids": [10, 11, 12] +} +``` + +**Backend Process (`auto_generate_ideas()` view):** +```python +# backend/igny8_core/modules/planner/views.py line 810 +def auto_generate_ideas(self, request): + cluster_ids = request.data.get('ids', []) + account = getattr(request, 'account', None) + + # Use IdeasService + service = IdeasService() + result = service.generate_ideas(cluster_ids, account) + + if result.get('success'): + if 'task_id' in result: + # Async task queued + return success_response(data={'task_id': result['task_id']}, message='Idea generation started') + else: + # Synchronous execution + return success_response(data=result) +``` + +**IdeasService Logic:** +**Location:** `backend/igny8_core/business/planning/services/ideas_service.py` + +1. **Load Clusters:** + - Fetch `Clusters` by IDs with keywords + - Get cluster name, description, keywords, volume + +2. **Check Credits:** + - Calculate credits needed (based on cluster count) + - Deduct credits from account + +3. **AI Idea Generation:** + - For each cluster: + - Send cluster data to AI engine + - Prompt: "Generate 5-10 content ideas for cluster '{name}' with keywords [{keywords}]..." + - AI returns: `[{ title, description, target_keywords, entity_type, cluster_role }]` + +4. **Create Ideas:** + - For each AI idea: + - Create `ContentIdeas` record + - Set `keyword_cluster_id` = cluster ID + - Set `status` = 'new' + - Set `site_entity_type`, `cluster_role` from AI + - Link keywords via `keyword_objects` M2M + +5. **Return Result:** + - Sync: `{ success: true, ideas_created: 25 }` + - Async: `{ success: true, task_id: 'uuid', message: 'Idea generation started' }` + +**Response (Async):** +```json +{ + "success": true, + "data": { + "task_id": "xyz789-uvw456-rst123" + }, + "message": "Idea generation started" +} +``` + +**AI Task Progress Phases:** +1. **Initializing** (0-10%) - Loading clusters, keywords +2. **Analyzing Clusters** (10-30%) - Analyzing keyword patterns +3. **Generating Ideas** (30-90%) - AI creating ideas (per cluster) +4. **Saving Ideas** (90-100%) - Creating ContentIdeas records + +--- + +#### Step 2.2: Queue Ideas to Writer +**Frontend:** Ideas Page β†’ Select ideas (status='new') β†’ Bulk Actions β†’ "Queue to Writer" +**Function:** `handleBulkAction('queue_to_writer', ids)` +**API Call:** `POST /v1/planner/content-ideas/bulk_queue_to_writer/` + +**Request Payload:** +```json +{ + "ids": [50, 51, 52] +} +``` + +**Backend Process (`bulk_queue_to_writer()` view):** +```python +# backend/igny8_core/modules/planner/views.py line 997 +def bulk_queue_to_writer(self, request): + ids = request.data.get('ids', []) + queryset = self.get_queryset() + ideas = queryset.filter(id__in=ids, status='new') # Only 'new' ideas + + created_tasks = [] + for idea in ideas: + task = Tasks.objects.create( + title=idea.idea_title, + description=idea.description or '', + keywords=idea.target_keywords or '', + cluster=idea.keyword_cluster, + idea=idea, + status='queued', + account=idea.account, + site=idea.site, + sector=idea.sector, + entity_type=(idea.site_entity_type or 'post'), + taxonomy=idea.taxonomy, + cluster_role=(idea.cluster_role or 'hub'), + ) + created_tasks.append(task.id) + + # Update idea status to 'scheduled' + idea.status = 'scheduled' + idea.save() + + return success_response(data={'created_count': len(created_tasks)}) +``` + +**Response:** +```json +{ + "success": true, + "data": { + "created_count": 3 + }, + "message": "Queue complete: 3 tasks created from 3 ideas" +} +``` + +--- + +### Stage 3: Content Generation + +#### Step 3.1: AI Generate Content for Task +**Frontend:** Tasks Page β†’ Row Action β†’ "Generate Content" +**Function:** `handleRowAction('generate_content', task)` +**API Call:** `POST /v1/writer/tasks/auto_generate_content/` + +**Request Payload:** +```json +{ + "ids": [100] +} +``` + +**Backend Process (`auto_generate_content()` view):** +```python +# backend/igny8_core/modules/writer/views.py line 150 +def auto_generate_content(self, request): + ids = request.data.get('ids', []) + account = getattr(request, 'account', None) + + # Validate tasks exist + existing_tasks = queryset.filter(id__in=ids, account=account) + + # Use ContentGenerationService + service = ContentGenerationService() + result = service.generate_content(ids, account) + + if result.get('success'): + if 'task_id' in result: + # Async task queued + return success_response(data={'task_id': result['task_id']}, message='Content generation started') + else: + # Synchronous execution + return success_response(data=result, message='Content generated successfully') +``` + +**ContentGenerationService Logic:** +**Location:** `backend/igny8_core/business/content/services/content_generation_service.py` + +1. **Load Tasks:** + - Fetch `Tasks` by IDs with cluster, keywords + - Validate task status (can generate for any status) + +2. **Check Credits:** + - Calculate credits needed (based on word count target) + - Deduct credits from account + +3. **AI Content Generation:** + - For each task: + - Build AI prompt with: + - Title: `task.title` + - Keywords: `task.keywords` or cluster keywords + - Entity type: `task.entity_type` + - Cluster role: `task.cluster_role` + - Word count: estimate from idea or default + - Send to AI engine + - AI returns: `{ html_content, word_count, meta_title, meta_description, primary_keyword, secondary_keywords }` + +4. **Create/Update Content:** + - Check if `task.content_record` exists (OneToOne) + - If exists: update content + - If not: create new `Content` record + - Set fields: + - `html_content` = AI output + - `word_count` = AI calculated + - `meta_title`, `meta_description` = AI SEO + - `primary_keyword`, `secondary_keywords` = AI keywords + - `status` = 'draft' + - `source` = 'igny8' + - `sync_status` = 'native' + - `entity_type` = `task.entity_type` + - `cluster_role` = `task.cluster_role` + - `cluster` = `task.cluster` + - `structure_data` = AI metadata + +5. **Update Task Status:** + - Set `task.status` = 'completed' + +6. **Return Result:** + - Sync: `{ success: true, tasks_updated: 1 }` + - Async: `{ success: true, task_id: 'uuid', message: 'Content generation started' }` + +**Response (Async):** +```json +{ + "success": true, + "data": { + "task_id": "content-abc123-def456" + }, + "message": "Content generation started" +} +``` + +**AI Task Progress Phases:** +1. **Initializing** (0-10%) - Loading tasks, keywords +2. **Research** (10-30%) - Analyzing keywords, cluster +3. **Outlining** (30-50%) - Creating content structure +4. **Writing** (50-90%) - Generating content sections +5. **SEO Optimization** (90-95%) - Adding meta tags, keywords +6. **Saving** (95-100%) - Creating Content record + +--- + +### Stage 4: Image Generation + +#### Step 4.1: Generate Image Prompts (Smart Prompts) +**Frontend:** Content Page β†’ Row Action β†’ "Generate Image Prompts" +**Function:** `handleRowAction('generate_image_prompts', content)` +**API Call:** `POST /v1/writer/content/generate_image_prompts/` + +**Request Payload:** +```json +{ + "ids": [200] +} +``` + +**Backend Process (`generate_image_prompts()` view):** +```python +# backend/igny8_core/modules/writer/views.py line 793 +@action(detail=False, methods=['post'], url_path='generate_image_prompts', url_name='generate_image_prompts') +def generate_image_prompts(self, request): + ids = request.data.get('ids', []) + account = getattr(request, 'account', None) + + # Use ImagePromptService + service = ImagePromptService() + result = service.generate_prompts(ids, account) + + if result.get('success'): + if 'task_id' in result: + return success_response(data={'task_id': result['task_id']}, message='Prompt generation started') + else: + return success_response(data=result) +``` + +**ImagePromptService Logic:** + +1. **Load Content:** + - Fetch `Content` by IDs with `html_content` + - Parse HTML to identify image placement needs + +2. **Analyze Content:** + - Identify sections needing images + - Extract context for each image (surrounding text) + - Determine image type (featured, in-article) + +3. **AI Prompt Generation:** + - For each image placement: + - Send section context to AI + - Prompt: "Create a detailed image prompt for section about '{context}'..." + - AI returns: detailed DALL-E/Stable Diffusion prompt + +4. **Create Image Records:** + - For featured image: create `Images` record with `image_type='featured'` + - For in-article images: create `Images` records with `image_type='in_article'`, `position=N` + - Set `prompt` = AI output + - Set `status` = 'pending' + - Set `provider`, `model` from settings + +5. **Return Result:** + - `{ success: true, prompts_created: 5 }` + +--- + +#### Step 4.2: Generate Images from Prompts +**Frontend:** Images Page β†’ Row Action β†’ "Generate Images" +**Function:** `handleGenerateImages(contentId)` +**Opens:** `ImageQueueModal` + +**Modal Process:** +1. **Fetch Settings:** + - `GET /v1/writer/images/settings/` + - Returns: `{ max_in_article_images, default_provider, default_model }` + +2. **Build Queue:** + - Load content images: `fetchContentImages({ content_id: contentId })` + - Filter images with `status='pending'` and `prompt` + - Order: featured first, then in-article by position + - Limit: max_in_article_images from settings + +3. **Generate Images Sequentially:** + - For each queue item: + - Update UI: status='generating', progress=10% + - Call: `POST /v1/writer/images/generate_images/` + ```json + { + "content_id": 200, + "image_type": "featured", + "prompt": "Professional coffee beans in burlap sack...", + "provider": "dall-e", + "model": "dall-e-3" + } + ``` + - Backend calls image provider API (DALL-E, Stable Diffusion) + - Updates `Images` record: + - `image_url` = provider response + - `status` = 'generated' or 'failed' + - `error_message` = if failed + - Update UI: status='generated', progress=100%, imageUrl=... + +4. **Completion:** + - All images generated + - Update overall status badge + - Reload images list + +**Backend Image Generation:** +```python +# backend/igny8_core/modules/writer/views.py line 634 +@action(detail=False, methods=['post'], url_path='generate_images', url_name='generate_images') +def generate_images(self, request): + content_id = request.data.get('content_id') + image_type = request.data.get('image_type') + prompt = request.data.get('prompt') + provider = request.data.get('provider', 'dall-e') + model = request.data.get('model', 'dall-e-3') + + # Deduct credits + # Call image provider API + # Update Images record with image_url or error + + return success_response(data={'image_url': 'https://...'}) +``` + +--- + +## AI Functions + +### 1. Auto-Cluster Keywords +**Function ID:** `ai-auto-cluster-01` +**API Endpoint:** `POST /v1/planner/keywords/auto_cluster/` +**Service:** `ClusteringService` +**Location:** `backend/igny8_core/business/planning/services/clustering_service.py` + +**Input:** +- `ids`: Keyword IDs (max 20) +- `sector_id`: Sector ID + +**Process:** +1. Load keywords with seed_keyword data +2. Check credits (1 credit per 5 keywords) +3. Send to AI: keyword texts, volumes, difficulties, intents +4. AI groups keywords into semantic clusters +5. Create/update Clusters records +6. Update Keywords.cluster_id + +**Output:** +- Async: `{ task_id }` +- Sync: `{ clusters_created, keywords_updated }` + +**Credits:** 1 credit per 5 keywords (rounded up) + +--- + +### 2. Auto-Generate Ideas +**Function ID:** `ai-generate-ideas-01-desktop` +**API Endpoint:** `POST /v1/planner/clusters/auto_generate_ideas/` +**Service:** `IdeasService` +**Location:** `backend/igny8_core/business/planning/services/ideas_service.py` + +**Input:** +- `ids`: Cluster IDs (max 5) + +**Process:** +1. Load clusters with keywords +2. Check credits (2 credits per cluster) +3. For each cluster: + - Send cluster name, keywords, volumes to AI + - AI generates 5-10 content ideas +4. Create ContentIdeas records + +**Output:** +- Async: `{ task_id }` +- Sync: `{ ideas_created }` + +**Credits:** 2 credits per cluster + +--- + +### 3. Auto-Generate Content +**Function ID:** `ai-generate-content-03` +**API Endpoint:** `POST /v1/writer/tasks/auto_generate_content/` +**Service:** `ContentGenerationService` +**Location:** `backend/igny8_core/business/content/services/content_generation_service.py` + +**Input:** +- `ids`: Task IDs (max 10) + +**Process:** +1. Load tasks with clusters, keywords +2. Check credits (based on word count target) +3. For each task: + - Build AI prompt with title, keywords, entity_type + - AI generates HTML content, meta tags, SEO +4. Create/update Content records +5. Update task status to 'completed' + +**Output:** +- Async: `{ task_id }` +- Sync: `{ tasks_updated }` + +**Credits:** 1 credit per 500 words (estimated) + +--- + +### 4. Generate Image Prompts +**Function ID:** `ai-generate-image-prompts-01-desktop` +**API Endpoint:** `POST /v1/writer/content/generate_image_prompts/` +**Service:** `ImagePromptService` +**Location:** `backend/igny8_core/business/content/services/image_prompt_service.py` + +**Input:** +- `ids`: Content IDs + +**Process:** +1. Load content with HTML +2. Analyze content sections +3. For each image placement: + - Send section context to AI + - AI creates detailed image prompt +4. Create Images records with prompts + +**Output:** +- Async: `{ task_id }` +- Sync: `{ prompts_created }` + +**Credits:** 0.5 credits per prompt + +--- + +### 5. Generate Images +**Function ID:** N/A (synchronous) +**API Endpoint:** `POST /v1/writer/images/generate_images/` +**Service:** Direct image provider call + +**Input:** +- `content_id`: Content ID +- `image_type`: 'featured' or 'in_article' +- `prompt`: Image generation prompt +- `provider`: 'dall-e', 'stable-diffusion', 'midjourney' +- `model`: Provider-specific model + +**Process:** +1. Deduct credits (based on provider/model) +2. Call image provider API (DALL-E, Stable Diffusion) +3. Update Images record with image_url or error + +**Output:** +- `{ image_url: 'https://...' }` + +**Credits:** +- DALL-E 3: 4 credits per image +- DALL-E 2: 2 credits per image +- Stable Diffusion: 1 credit per image + +--- + +## Flowchart Diagrams + +### Complete Workflow Flowchart + +```mermaid +graph TD + Start([User starts workflow]) --> SelectSite[Select Site & Sector] + SelectSite --> AttachKeywords[Attach SeedKeywords
POST /keywords/] + + AttachKeywords --> ManualCluster{Manual or
AI Clustering?} + ManualCluster -->|Manual| EditKeywords[Edit Keywords
Assign to Cluster] + ManualCluster -->|AI| AutoCluster[Auto-Cluster
POST /keywords/auto_cluster/] + + AutoCluster --> ClusteringProgress{Async
Task?} + ClusteringProgress -->|Yes| PollClustering[Poll Progress
GET /task-progress/:id/] + ClusteringProgress -->|No| ClustersCreated[Clusters Created] + PollClustering --> ClustersCreated + EditKeywords --> ClustersCreated + + ClustersCreated --> ManualIdeas{Manual or
AI Ideas?} + ManualIdeas -->|Manual| CreateIdeas[Create Ideas Manually
POST /content-ideas/] + ManualIdeas -->|AI| AutoIdeas[Auto-Generate Ideas
POST /clusters/auto_generate_ideas/] + + AutoIdeas --> IdeasProgress{Async
Task?} + IdeasProgress -->|Yes| PollIdeas[Poll Progress
GET /task-progress/:id/] + IdeasProgress -->|No| IdeasCreated[Ideas Created] + PollIdeas --> IdeasCreated + CreateIdeas --> IdeasCreated + + IdeasCreated --> QueueToWriter[Queue to Writer
POST /content-ideas/bulk_queue_to_writer/] + QueueToWriter --> TasksCreated[Tasks Created
status='queued'] + + TasksCreated --> ManualContent{Manual or
AI Content?} + ManualContent -->|Manual| WriteContent[Write Content Manually] + ManualContent -->|AI| GenerateContent[Generate Content
POST /tasks/auto_generate_content/] + + GenerateContent --> ContentProgress{Async
Task?} + ContentProgress -->|Yes| PollContent[Poll Progress
GET /task-progress/:id/] + ContentProgress -->|No| ContentCreated[Content Created] + PollContent --> ContentCreated + WriteContent --> ContentCreated + + ContentCreated --> ImagePrompts{Generate
Image Prompts?} + ImagePrompts -->|Yes| AIPrompts[Generate Image Prompts
POST /content/generate_image_prompts/] + ImagePrompts -->|No| ManualPrompts[Write Prompts Manually] + + AIPrompts --> PromptsCreated[Image Records Created
status='pending'] + ManualPrompts --> PromptsCreated + + PromptsCreated --> GenerateImages{Generate
Images?} + GenerateImages -->|Yes| ImageQueue[Open ImageQueueModal
Sequential Generation] + GenerateImages -->|No| SkipImages[Skip Images] + + ImageQueue --> ImageLoop[For Each Image:
POST /images/generate_images/] + ImageLoop --> ImageCreated[Image Generated
status='generated'] + ImageCreated --> MoreImages{More
Images?} + MoreImages -->|Yes| ImageLoop + MoreImages -->|No| AllImagesComplete[All Images Complete] + + AllImagesComplete --> ReviewContent[Review Content
status='review'] + SkipImages --> ReviewContent + + ReviewContent --> PublishContent[Publish Content
status='publish'] + PublishContent --> End([Workflow Complete]) + + %% Styling + classDef aiFunction fill:#9f7aea,stroke:#805ad5,stroke-width:2px,color:#fff + classDef userAction fill:#48bb78,stroke:#38a169,stroke-width:2px,color:#fff + classDef decision fill:#ed8936,stroke:#dd6b20,stroke-width:2px,color:#fff + classDef progress fill:#4299e1,stroke:#3182ce,stroke-width:2px,color:#fff + + class AutoCluster,AutoIdeas,GenerateContent,AIPrompts,ImageQueue aiFunction + class AttachKeywords,EditKeywords,CreateIdeas,WriteContent,ManualPrompts,ReviewContent,PublishContent userAction + class ManualCluster,ManualIdeas,ManualContent,ImagePrompts,GenerateImages,ClusteringProgress,IdeasProgress,ContentProgress,MoreImages decision + class PollClustering,PollIdeas,PollContent progress +``` + +--- + +### AI Function Progress Tracking + +```mermaid +sequenceDiagram + participant U as User (Frontend) + participant A as API Endpoint + participant S as Service Layer + participant AI as AI Engine + participant C as Celery Worker + participant DB as Database + + U->>A: POST /keywords/auto_cluster/ {ids: [1,2,3]} + A->>S: ClusteringService.cluster_keywords() + S->>DB: Check credits + DB-->>S: Credits sufficient + S->>C: Queue async task + C-->>S: task_id + S-->>A: {success: true, task_id: 'abc123'} + A-->>U: {task_id: 'abc123', message: 'Clustering started'} + + U->>U: Open ProgressModal + + loop Poll every 2 seconds + U->>A: GET /task-progress/abc123/ + A->>DB: Get task progress + DB-->>A: {phase, percentage, message} + A-->>U: {status, percentage, message, details} + U->>U: Update progress bar + end + + Note over C,AI: Celery worker processes task + C->>AI: Send keywords to AI + AI-->>C: {clusters: [{name, keywords}]} + C->>DB: Create Clusters, update Keywords + DB-->>C: Success + C->>DB: Update task progress: 100%, completed + + U->>A: GET /task-progress/abc123/ + A->>DB: Get task progress + DB-->>A: {status: 'completed', percentage: 100} + A-->>U: {status: 'completed', percentage: 100} + U->>U: Close modal, show success toast + U->>A: GET /keywords/ (reload data) + A->>DB: Fetch updated keywords + DB-->>A: Keywords with cluster_id set + A-->>U: Updated keywords list +``` + +--- + +### Image Generation Queue Flow + +```mermaid +sequenceDiagram + participant U as User (Frontend) + participant M as ImageQueueModal + participant A as API Endpoint + participant P as Image Provider + participant DB as Database + + U->>A: GET /images/content/?content_id=200 + A->>DB: Fetch content images + DB-->>A: {featured_image, in_article_images[]} + A-->>U: Content images data + + U->>U: Click "Generate Images" + U->>A: GET /images/settings/ + A-->>U: {max_in_article_images: 3, default_provider: 'dall-e'} + + U->>M: Open ImageQueueModal + M->>M: Build queue: [featured, in-article-1, in-article-2, in-article-3] + M->>U: Display queue items (status: pending) + + loop For each queue item + M->>M: Update UI: status='generating', progress=10% + M->>A: POST /images/generate_images/ {content_id, image_type, prompt, provider, model} + A->>DB: Deduct credits + A->>P: Generate image (DALL-E API) + P-->>A: {image_url: 'https://...'} + A->>DB: Update Images record (image_url, status='generated') + DB-->>A: Success + A-->>M: {success: true, image_url: 'https://...'} + M->>M: Update UI: status='generated', progress=100%, show image + end + + M->>M: All images complete + M->>U: Show success message, close modal option + U->>U: Close modal + U->>A: GET /images/content/ (reload) + A->>DB: Fetch updated images + DB-->>A: All images with status='generated' + A-->>U: Updated images list +``` + +--- + +## Summary + +This workflow documentation covers: + +βœ… **Database Tables:** All 6 core models with fields, types, indexes, and relationships +βœ… **Frontend Pages:** 6 pages with all functions, API calls, and state management +βœ… **Backend APIs:** All endpoints with methods, functions, and ViewSets +βœ… **Complete Workflow:** Step-by-step from keywords to published content +βœ… **AI Functions:** 5 AI functions with inputs, processes, outputs, and credits +βœ… **Flowcharts:** 3 detailed diagrams (complete workflow, progress tracking, image generation) + +**Key Workflow Paths:** + +1. **Manual Path:** User creates everything manually (keywords β†’ clusters β†’ ideas β†’ tasks β†’ content) +2. **AI-Assisted Path:** User uses AI functions at each stage (auto-cluster, auto-generate ideas, auto-generate content) +3. **Hybrid Path:** Mix of manual and AI (manual keywords, AI clustering, manual ideas, AI content) + +**Credits System:** +- Auto-Cluster: 1 credit per 5 keywords +- Generate Ideas: 2 credits per cluster +- Generate Content: 1 credit per 500 words +- Image Prompts: 0.5 credits per prompt +- Generate Images: 1-4 credits per image (provider-dependent) + +**Progress Tracking:** +All async AI functions return `task_id`, which is polled via `/task-progress/:id/` to show real-time progress with phases, percentages, and step logs. + +--- + +**End of Documentation**