50 KiB
IGNY8 Planner → Writer Workflow Documentation
Complete Technical Implementation Guide
Date: November 24, 2025
Version: 1.0
Table of Contents
- Overview
- Database Tables & Models
- Frontend Pages & Components
- Backend API Endpoints
- Complete Workflow
- AI Functions
- 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_keywordstatusclustersite, sectorseed_keyword, site, sector(unique)
Properties (Computed from SeedKeyword):
keyword- Text from seed_keyword.keywordvolume- volume_override or seed_keyword.volumedifficulty- difficulty_override or seed_keyword.difficultyintent- 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:
namestatussite, sectorcontext_type
Relationships:
keywords(1:M) - Related Keywordsideas(1:M) - Related ContentIdeastasks(1:M) - Related Taskscontents(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_titlestatuskeyword_clustersite_entity_typecluster_rolesite, 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:
titlestatusclusterentity_typecluster_rolesite, 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:
taskgenerated_atsourcesync_statussource, sync_statusentity_typecontent_formatcluster_roleclusterexternal_typesite, 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:
contentimage_typestatussite, 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/paginationhandleCreateSeedKeyword()- Attach SeedKeyword to site/sectorhandleEdit(keyword)- Edit keyword (override volume/difficulty, assign cluster)handleBulkAction('auto_cluster', ids)- AI clustering (max 20 keywords)handleBulkDelete(ids)- Delete keywordshandleBulkUpdateStatus(ids, status)- Update statushandleExport()- Export keywords to CSVhandleImportClick()- Import keywords from CSV
API Calls:
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 listclusters- Cluster dropdown optionsavailableSeedKeywords- Available SeedKeywords for attachmentsearchTerm,statusFilter,clusterFilter,intentFilter,difficultyFilter,volumeMin,volumeMax- FilterscurrentPage,totalPages,totalCount- PaginationsortBy,sortDirection- SortingselectedIds- 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 APIhandleCreateCluster()- Create new cluster manuallyhandleEdit(cluster)- Edit cluster name/descriptionhandleRowAction('generate_ideas', cluster)- Generate ideas for single clusterhandleBulkAction('auto_generate_ideas', ids)- Generate ideas for multiple clusters (max 5)handleBulkDelete(ids)- Delete clustershandleBulkUpdateStatus(ids, status)- Update status
API Calls:
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 listsearchTerm,statusFilter,difficultyFilter,volumeMin,volumeMax- FilterscurrentPage,totalPages,totalCount- PaginationsortBy,sortDirection- SortingselectedIds- 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 APIhandleCreateIdea()- Create new idea manuallyhandleEdit(idea)- Edit idea title/description/clusterhandleRowAction('queue_to_writer', idea)- Convert single idea to taskhandleBulkAction('queue_to_writer', ids)- Convert multiple ideas to taskshandleBulkDelete(ids)- Delete ideashandleBulkUpdateStatus(ids, status)- Update status
API Calls:
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 listclusters- Cluster dropdown optionssearchTerm,statusFilter,clusterFilter,structureFilter,typeFilter,entityTypeFilter- FilterscurrentPage,totalPages,totalCount- PaginationsortBy,sortDirection- SortingselectedIds- Bulk selection
Queue to Writer Logic:
Only ideas with status='new' can be queued. Creates Tasks with:
title=idea_titledescription=idea.descriptionkeywords=idea.target_keywordscluster=idea.keyword_clusteridea=idea.idstatus= 'queued'entity_type=idea.site_entity_typecluster_role=idea.cluster_roletaxonomy=idea.taxonomy
Writer Module Pages
4. Tasks Page (/writer/tasks)
Component: frontend/src/pages/Writer/Tasks.tsx
Key Functions:
loadTasks()- Fetch tasks from APIhandleCreateTask()- Create new task manuallyhandleEdit(task)- Edit task title/description/clusterhandleRowAction('generate_content', task)- Generate content for single task (AI)handleBulkAction('generate_images', ids)- Generate images for multiple tasks (max 10)handleBulkDelete(ids)- Delete taskshandleBulkUpdateStatus(ids, status)- Update status
API Calls:
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 listclusters- Cluster dropdown optionssearchTerm,statusFilter,clusterFilter,structureFilter,typeFilter,sourceFilter,entityTypeFilter- FilterscurrentPage,totalPages,totalCount- PaginationsortBy,sortDirection- SortingselectedIds- Bulk selectionaiLogs- 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 APIhandleRowAction('generate_image_prompts', content)- Generate AI image promptshandleRowAction('optimize', content)- Run optimizer on contenthandleRowAction('send_to_optimizer', content)- Navigate to optimizer page
API Calls:
fetchContent(filters: ContentFilters): Promise<PaginatedResponse<Content>>
generateImagePrompts(contentIds: number[]): Promise<AITaskResponse>
optimizerApi.optimize(contentId: number, source: string): Promise<OptimizationResult>
State Management:
content- Content listsearchTerm,statusFilter,sourceFilter,syncStatusFilter- FilterscurrentPage,totalPages,totalCount- PaginationsortBy,sortDirection- SortingselectedIds- Bulk selection
Content Row Actions:
- View - Navigate to
/writer/content/:id(ContentView page) - Generate Image Prompts - AI function to create smart image prompts
- Optimize - Run optimizer to improve SEO scores
- 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 contenthandleRowAction('update_status', imageGroup)- Update all images for contenthandleGenerateImages(contentId)- Generate images using AI (opens ImageQueueModal)handleBulkExport(ids)- Export image metadata
API Calls:
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- FilterscurrentPage,totalPages,totalCount- Client-side paginationsortBy,sortDirection- Client-side sortingselectedIds- Bulk selectionimageQueue- Image generation queue itemstaskId,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:
{
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:
{
"seed_keyword_id": 123,
"site_id": 1,
"sector_id": 2,
"volume_override": null,
"difficulty_override": null,
"cluster_id": null,
"status": "pending"
}
Backend Process:
- Validate
seed_keyword_idexists inseed_keywordstable - Validate
site_idandsector_idexist and match - Check unique constraint:
seed_keyword + site + sector - Create
Keywordsrecord with account/site/sector - Return created keyword with computed properties
Response:
{
"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:
{
"ids": [456, 457, 458, 459, 460],
"sector_id": 2
}
Backend Process (auto_cluster() view):
# 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
-
Load Keywords:
- Fetch
Keywordsby IDs - Get keyword text, volume, difficulty, intent from seed_keyword
- Fetch
-
Check Credits:
- Calculate credits needed (based on keyword count)
- Deduct credits from account
-
AI Clustering:
- Send keywords to AI engine
- Prompt: "Group these keywords into semantic clusters..."
- AI returns:
{ clusters: [{ name, keywords: [ids] }] }
-
Create/Update Clusters:
- For each AI cluster:
- Check if cluster with same name exists
- If exists, use existing; else create new
Clustersrecord - Update
Keywords.cluster_idfor all keywords in group - Calculate
keywords_count,volume,difficultyfor cluster
- For each AI cluster:
-
Return Result:
- Sync:
{ success: true, clusters_created: 3, keywords_updated: 20 } - Async:
{ success: true, task_id: 'uuid', message: 'Clustering started' }
- Sync:
Response (Async):
{
"success": true,
"data": {
"task_id": "abc123-def456-ghi789"
},
"message": "Clustering started"
}
Frontend Progress Tracking:
// 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:
- Initializing (0-10%) - Validating keywords, loading data
- Processing (10-40%) - Sending to AI engine
- AI Analysis (40-80%) - AI clustering keywords
- Saving Results (80-95%) - Creating clusters, updating keywords
- 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:
{
"ids": [10, 11, 12]
}
Backend Process (auto_generate_ideas() view):
# 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
-
Load Clusters:
- Fetch
Clustersby IDs with keywords - Get cluster name, description, keywords, volume
- Fetch
-
Check Credits:
- Calculate credits needed (based on cluster count)
- Deduct credits from account
-
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 }]
- For each cluster:
-
Create Ideas:
- For each AI idea:
- Create
ContentIdeasrecord - Set
keyword_cluster_id= cluster ID - Set
status= 'new' - Set
site_entity_type,cluster_rolefrom AI - Link keywords via
keyword_objectsM2M
- Create
- For each AI idea:
-
Return Result:
- Sync:
{ success: true, ideas_created: 25 } - Async:
{ success: true, task_id: 'uuid', message: 'Idea generation started' }
- Sync:
Response (Async):
{
"success": true,
"data": {
"task_id": "xyz789-uvw456-rst123"
},
"message": "Idea generation started"
}
AI Task Progress Phases:
- Initializing (0-10%) - Loading clusters, keywords
- Analyzing Clusters (10-30%) - Analyzing keyword patterns
- Generating Ideas (30-90%) - AI creating ideas (per cluster)
- 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:
{
"ids": [50, 51, 52]
}
Backend Process (bulk_queue_to_writer() view):
# 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:
{
"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:
{
"ids": [100]
}
Backend Process (auto_generate_content() view):
# 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
-
Load Tasks:
- Fetch
Tasksby IDs with cluster, keywords - Validate task status (can generate for any status)
- Fetch
-
Check Credits:
- Calculate credits needed (based on word count target)
- Deduct credits from account
-
AI Content Generation:
- For each task:
- Build AI prompt with:
- Title:
task.title - Keywords:
task.keywordsor cluster keywords - Entity type:
task.entity_type - Cluster role:
task.cluster_role - Word count: estimate from idea or default
- Title:
- Send to AI engine
- AI returns:
{ html_content, word_count, meta_title, meta_description, primary_keyword, secondary_keywords }
- Build AI prompt with:
- For each task:
-
Create/Update Content:
- Check if
task.content_recordexists (OneToOne) - If exists: update content
- If not: create new
Contentrecord - Set fields:
html_content= AI outputword_count= AI calculatedmeta_title,meta_description= AI SEOprimary_keyword,secondary_keywords= AI keywordsstatus= 'draft'source= 'igny8'sync_status= 'native'entity_type=task.entity_typecluster_role=task.cluster_rolecluster=task.clusterstructure_data= AI metadata
- Check if
-
Update Task Status:
- Set
task.status= 'completed'
- Set
-
Return Result:
- Sync:
{ success: true, tasks_updated: 1 } - Async:
{ success: true, task_id: 'uuid', message: 'Content generation started' }
- Sync:
Response (Async):
{
"success": true,
"data": {
"task_id": "content-abc123-def456"
},
"message": "Content generation started"
}
AI Task Progress Phases:
- Initializing (0-10%) - Loading tasks, keywords
- Research (10-30%) - Analyzing keywords, cluster
- Outlining (30-50%) - Creating content structure
- Writing (50-90%) - Generating content sections
- SEO Optimization (90-95%) - Adding meta tags, keywords
- 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:
{
"ids": [200]
}
Backend Process (generate_image_prompts() view):
# 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:
-
Load Content:
- Fetch
Contentby IDs withhtml_content - Parse HTML to identify image placement needs
- Fetch
-
Analyze Content:
- Identify sections needing images
- Extract context for each image (surrounding text)
- Determine image type (featured, in-article)
-
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
- For each image placement:
-
Create Image Records:
- For featured image: create
Imagesrecord withimage_type='featured' - For in-article images: create
Imagesrecords withimage_type='in_article',position=N - Set
prompt= AI output - Set
status= 'pending' - Set
provider,modelfrom settings
- For featured image: create
-
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:
-
Fetch Settings:
GET /v1/writer/images/settings/- Returns:
{ max_in_article_images, default_provider, default_model }
-
Build Queue:
- Load content images:
fetchContentImages({ content_id: contentId }) - Filter images with
status='pending'andprompt - Order: featured first, then in-article by position
- Limit: max_in_article_images from settings
- Load content images:
-
Generate Images Sequentially:
- For each queue item:
- Update UI: status='generating', progress=10%
- Call:
POST /v1/writer/images/generate_images/{ "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
Imagesrecord:image_url= provider responsestatus= 'generated' or 'failed'error_message= if failed
- Update UI: status='generated', progress=100%, imageUrl=...
- For each queue item:
-
Completion:
- All images generated
- Update overall status badge
- Reload images list
Backend Image Generation:
# 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:
- Load keywords with seed_keyword data
- Check credits (1 credit per 5 keywords)
- Send to AI: keyword texts, volumes, difficulties, intents
- AI groups keywords into semantic clusters
- Create/update Clusters records
- 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:
- Load clusters with keywords
- Check credits (2 credits per cluster)
- For each cluster:
- Send cluster name, keywords, volumes to AI
- AI generates 5-10 content ideas
- 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:
- Load tasks with clusters, keywords
- Check credits (based on word count target)
- For each task:
- Build AI prompt with title, keywords, entity_type
- AI generates HTML content, meta tags, SEO
- Create/update Content records
- 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:
- Load content with HTML
- Analyze content sections
- For each image placement:
- Send section context to AI
- AI creates detailed image prompt
- 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 IDimage_type: 'featured' or 'in_article'prompt: Image generation promptprovider: 'dall-e', 'stable-diffusion', 'midjourney'model: Provider-specific model
Process:
- Deduct credits (based on provider/model)
- Call image provider API (DALL-E, Stable Diffusion)
- 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
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
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
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:
- Manual Path: User creates everything manually (keywords → clusters → ideas → tasks → content)
- AI-Assisted Path: User uses AI functions at each stage (auto-cluster, auto-generate ideas, auto-generate content)
- 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