1467 lines
50 KiB
Markdown
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**
|