Files
igny8/planner-writer-workflow.md
alorig 0bd603f925 docs
2025-11-24 11:52:43 +05:00

1467 lines
50 KiB
Markdown

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