# 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**