# 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 | ContentTaxonomy |
| `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 | ContentTaxonomy |
| `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<PaginatedResponse<Keyword>>
createKeyword(data: KeywordCreateData): Promise<Keyword>
updateKeyword(id: number, data: KeywordCreateData): Promise<Keyword>
deleteKeyword(id: number): Promise<void>
bulkDeleteKeywords(ids: number[]): Promise<{ deleted_count: number }>
bulkUpdateKeywordsStatus(ids: number[], status: string): Promise<{ updated_count: number }>
autoClusterKeywords(ids: number[], sectorId?: number): Promise<AITaskResponse>
```

**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<PaginatedResponse<Cluster>>
createCluster(data: ClusterCreateData): Promise<Cluster>
updateCluster(id: number, data: ClusterCreateData): Promise<Cluster>
deleteCluster(id: number): Promise<void>
bulkDeleteClusters(ids: number[]): Promise<{ deleted_count: number }>
bulkUpdateClustersStatus(ids: number[], status: string): Promise<{ updated_count: number }>
autoGenerateIdeas(clusterIds: number[]): Promise<AITaskResponse>
```

**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<PaginatedResponse<ContentIdea>>
createContentIdea(data: ContentIdeaCreateData): Promise<ContentIdea>
updateContentIdea(id: number, data: ContentIdeaCreateData): Promise<ContentIdea>
deleteContentIdea(id: number): Promise<void>
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<PaginatedResponse<Task>>
createTask(data: TaskCreateData): Promise<Task>
updateTask(id: number, data: TaskCreateData): Promise<Task>
deleteTask(id: number): Promise<void>
bulkDeleteTasks(ids: number[]): Promise<{ deleted_count: number }>
bulkUpdateTasksStatus(ids: number[], status: string): Promise<{ updated_count: number }>
autoGenerateContent(taskIds: number[]): Promise<AITaskResponse>
autoGenerateImages(taskIds: number[]): Promise<AITaskResponse>
```

**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<PaginatedResponse<Content>>
generateImagePrompts(contentIds: number[]): Promise<AITaskResponse>
optimizerApi.optimize(contentId: number, source: string): Promise<OptimizationResult>
```

**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<ContentImagesResponse>
fetchImageGenerationSettings(): Promise<ImageGenerationSettings>
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<br/>POST /keywords/]
    
    AttachKeywords --> ManualCluster{Manual or<br/>AI Clustering?}
    ManualCluster -->|Manual| EditKeywords[Edit Keywords<br/>Assign to Cluster]
    ManualCluster -->|AI| AutoCluster[Auto-Cluster<br/>POST /keywords/auto_cluster/]
    
    AutoCluster --> ClusteringProgress{Async<br/>Task?}
    ClusteringProgress -->|Yes| PollClustering[Poll Progress<br/>GET /task-progress/:id/]
    ClusteringProgress -->|No| ClustersCreated[Clusters Created]
    PollClustering --> ClustersCreated
    EditKeywords --> ClustersCreated
    
    ClustersCreated --> ManualIdeas{Manual or<br/>AI Ideas?}
    ManualIdeas -->|Manual| CreateIdeas[Create Ideas Manually<br/>POST /content-ideas/]
    ManualIdeas -->|AI| AutoIdeas[Auto-Generate Ideas<br/>POST /clusters/auto_generate_ideas/]
    
    AutoIdeas --> IdeasProgress{Async<br/>Task?}
    IdeasProgress -->|Yes| PollIdeas[Poll Progress<br/>GET /task-progress/:id/]
    IdeasProgress -->|No| IdeasCreated[Ideas Created]
    PollIdeas --> IdeasCreated
    CreateIdeas --> IdeasCreated
    
    IdeasCreated --> QueueToWriter[Queue to Writer<br/>POST /content-ideas/bulk_queue_to_writer/]
    QueueToWriter --> TasksCreated[Tasks Created<br/>status='queued']
    
    TasksCreated --> ManualContent{Manual or<br/>AI Content?}
    ManualContent -->|Manual| WriteContent[Write Content Manually]
    ManualContent -->|AI| GenerateContent[Generate Content<br/>POST /tasks/auto_generate_content/]
    
    GenerateContent --> ContentProgress{Async<br/>Task?}
    ContentProgress -->|Yes| PollContent[Poll Progress<br/>GET /task-progress/:id/]
    ContentProgress -->|No| ContentCreated[Content Created]
    PollContent --> ContentCreated
    WriteContent --> ContentCreated
    
    ContentCreated --> ImagePrompts{Generate<br/>Image Prompts?}
    ImagePrompts -->|Yes| AIPrompts[Generate Image Prompts<br/>POST /content/generate_image_prompts/]
    ImagePrompts -->|No| ManualPrompts[Write Prompts Manually]
    
    AIPrompts --> PromptsCreated[Image Records Created<br/>status='pending']
    ManualPrompts --> PromptsCreated
    
    PromptsCreated --> GenerateImages{Generate<br/>Images?}
    GenerateImages -->|Yes| ImageQueue[Open ImageQueueModal<br/>Sequential Generation]
    GenerateImages -->|No| SkipImages[Skip Images]
    
    ImageQueue --> ImageLoop[For Each Image:<br/>POST /images/generate_images/]
    ImageLoop --> ImageCreated[Image Generated<br/>status='generated']
    ImageCreated --> MoreImages{More<br/>Images?}
    MoreImages -->|Yes| ImageLoop
    MoreImages -->|No| AllImagesComplete[All Images Complete]
    
    AllImagesComplete --> ReviewContent[Review Content<br/>status='review']
    SkipImages --> ReviewContent
    
    ReviewContent --> PublishContent[Publish Content<br/>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**
