docs & ux improvmeents
This commit is contained in:
462
docs/90-REFERENCE/AI-FUNCTIONS.md
Normal file
462
docs/90-REFERENCE/AI-FUNCTIONS.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# AI Functions Reference
|
||||
|
||||
**Last Verified:** December 25, 2025
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
IGNY8's AI engine provides functions for content planning and generation. Located in `backend/igny8_core/ai/`.
|
||||
|
||||
**Providers:**
|
||||
- **OpenAI** - GPT-4 for text, DALL-E 3 for images
|
||||
- **Runware** - Alternative image generation
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ AI ENGINE │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ AIEngine │───►│ Function │───►│ Provider │ │
|
||||
│ │ (router) │ │ (logic) │ │ (API) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ Functions: │
|
||||
│ • AutoClusterKeywords │
|
||||
│ • GenerateContentIdeas │
|
||||
│ • GenerateContent │
|
||||
│ • GenerateImages │
|
||||
│ • OptimizeContent (pending) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AIEngine
|
||||
|
||||
**Location:** `backend/igny8_core/ai/engine.py`
|
||||
|
||||
Main orchestrator for AI operations.
|
||||
|
||||
```python
|
||||
class AIEngine:
|
||||
def __init__(self, account: Account):
|
||||
self.account = account
|
||||
self.settings = self._load_settings()
|
||||
self.text_provider = self._init_text_provider()
|
||||
self.image_provider = self._init_image_provider()
|
||||
|
||||
def auto_cluster(self, keywords: List[Keyword]) -> List[Cluster]:
|
||||
"""Cluster keywords by topic"""
|
||||
|
||||
def generate_ideas(self, cluster: Cluster) -> List[ContentIdea]:
|
||||
"""Generate content ideas for cluster"""
|
||||
|
||||
def generate_content(self, task: Task) -> Content:
|
||||
"""Generate full article content"""
|
||||
|
||||
def generate_images(self, content: Content, count: int = 1) -> List[ContentImage]:
|
||||
"""Generate images for content"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Function: AutoClusterKeywords
|
||||
|
||||
**Purpose:** Group semantically related keywords into clusters
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"keywords": [
|
||||
{"id": "...", "keyword": "python tutorial"},
|
||||
{"id": "...", "keyword": "learn python"},
|
||||
{"id": "...", "keyword": "python basics"},
|
||||
...
|
||||
],
|
||||
"site_context": {
|
||||
"name": "Tech Blog",
|
||||
"industry": "Technology"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"clusters": [
|
||||
{
|
||||
"name": "Python Learning Resources",
|
||||
"description": "Tutorials and guides for learning Python",
|
||||
"keywords": ["python tutorial", "learn python", "python basics"]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `auto_cluster`
|
||||
|
||||
**Model:** GPT-4
|
||||
|
||||
**Credit Cost:** 1 idea credit per operation
|
||||
|
||||
---
|
||||
|
||||
## Function: GenerateContentIdeas
|
||||
|
||||
**Purpose:** Create content ideas from a keyword cluster
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"cluster": {
|
||||
"name": "Python Learning Resources",
|
||||
"description": "...",
|
||||
"keywords": [...]
|
||||
},
|
||||
"site_context": {
|
||||
"name": "Tech Blog",
|
||||
"industry": "Technology"
|
||||
},
|
||||
"count": 5 # Number of ideas to generate
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"ideas": [
|
||||
{
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"description": "A comprehensive guide covering...",
|
||||
"primary_keyword": "python tutorial",
|
||||
"secondary_keywords": ["learn python", "python basics"]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `generate_ideas`
|
||||
|
||||
**Model:** GPT-4
|
||||
|
||||
**Credit Cost:** 1 idea credit per idea generated
|
||||
|
||||
---
|
||||
|
||||
## Function: GenerateContent
|
||||
|
||||
**Purpose:** Create full article from task brief
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"task": {
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"brief": "Write a comprehensive guide...",
|
||||
"primary_keyword": "python tutorial",
|
||||
"secondary_keywords": ["learn python", "python basics"]
|
||||
},
|
||||
"site_context": {
|
||||
"name": "Tech Blog",
|
||||
"industry": "Technology",
|
||||
"tone": "Professional but approachable"
|
||||
},
|
||||
"options": {
|
||||
"target_word_count": 2000,
|
||||
"include_headings": True,
|
||||
"include_lists": True,
|
||||
"include_code_blocks": True
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"body": "<h2>Introduction</h2><p>...</p>...", # Full HTML
|
||||
"excerpt": "Learn the essential Python tips...",
|
||||
"meta_title": "10 Python Tips for Beginners | Tech Blog",
|
||||
"meta_description": "Master Python with these 10 essential tips...",
|
||||
"word_count": 2150
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `generate_content`
|
||||
|
||||
**Model:** GPT-4
|
||||
|
||||
**Credit Cost:** 1 content credit per generation
|
||||
|
||||
---
|
||||
|
||||
## Function: GenerateImages
|
||||
|
||||
**Purpose:** Create images for article content
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"content": {
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"body": "<html>...</html>",
|
||||
"primary_keyword": "python tutorial"
|
||||
},
|
||||
"options": {
|
||||
"count": 3,
|
||||
"style": "photorealistic", # photorealistic, illustration, diagram
|
||||
"size": "1024x1024"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Process:**
|
||||
1. Analyze content to identify image opportunities
|
||||
2. Generate prompts for each image
|
||||
3. Call image provider API
|
||||
4. Store images and generate thumbnails
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"url": "https://storage.../image1.png",
|
||||
"thumbnail_url": "https://storage.../image1_thumb.png",
|
||||
"alt_text": "Python code example showing...",
|
||||
"caption": "Example of Python list comprehension",
|
||||
"prompt": "A clean code editor showing Python syntax...",
|
||||
"is_featured": True
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `generate_image_prompts`
|
||||
|
||||
**Model:** DALL-E 3 or Runware
|
||||
|
||||
**Credit Cost:** 1 image credit per image
|
||||
|
||||
---
|
||||
|
||||
## Function: OptimizeContent (Pending)
|
||||
|
||||
**Status:** ⏸️ Not yet implemented
|
||||
|
||||
**Purpose:** SEO optimize existing content
|
||||
|
||||
**Planned Input:**
|
||||
```python
|
||||
{
|
||||
"content": {
|
||||
"title": "...",
|
||||
"body": "...",
|
||||
"target_keyword": "..."
|
||||
},
|
||||
"optimization_type": "seo" | "readability" | "both"
|
||||
}
|
||||
```
|
||||
|
||||
**Planned Output:**
|
||||
```python
|
||||
{
|
||||
"optimized_title": "...",
|
||||
"optimized_body": "...",
|
||||
"changes": [
|
||||
{"type": "keyword_density", "description": "..."},
|
||||
{"type": "heading_structure", "description": "..."},
|
||||
...
|
||||
],
|
||||
"score_before": 65,
|
||||
"score_after": 85
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Templates
|
||||
|
||||
### System Prompts
|
||||
|
||||
Stored in `PromptTemplate` model or defaults in code.
|
||||
|
||||
| Template | Purpose |
|
||||
|----------|---------|
|
||||
| `auto_cluster` | Keywords → Clusters |
|
||||
| `generate_ideas` | Cluster → Ideas |
|
||||
| `generate_content` | Task → Article |
|
||||
| `generate_image_prompts` | Content → Image prompts |
|
||||
| `optimize_content` | Content → Optimized (pending) |
|
||||
|
||||
### Template Variables
|
||||
|
||||
```python
|
||||
{
|
||||
"site_name": "Tech Blog",
|
||||
"site_industry": "Technology",
|
||||
"site_tone": "Professional",
|
||||
"keyword": "python tutorial",
|
||||
"keywords": ["python", "tutorial", "learn"],
|
||||
"cluster_name": "Python Learning",
|
||||
"task_title": "10 Python Tips",
|
||||
"task_brief": "Write a guide...",
|
||||
"target_word_count": 2000
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Prompts
|
||||
|
||||
Users can customize prompts via Settings → Prompts:
|
||||
|
||||
```python
|
||||
# API
|
||||
GET /api/v1/system/prompts/{type}/
|
||||
PUT /api/v1/system/prompts/{type}/
|
||||
POST /api/v1/system/prompts/{type}/reset/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider: OpenAI
|
||||
|
||||
**Location:** `backend/igny8_core/ai/providers/openai_provider.py`
|
||||
|
||||
### Text Generation
|
||||
|
||||
```python
|
||||
class OpenAITextProvider:
|
||||
def complete(self, prompt: str, options: dict) -> str:
|
||||
response = openai.ChatCompletion.create(
|
||||
model=options.get('model', 'gpt-4'),
|
||||
messages=[
|
||||
{"role": "system", "content": self.system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=options.get('temperature', 0.7),
|
||||
max_tokens=options.get('max_tokens', 4000)
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
```
|
||||
|
||||
### Image Generation
|
||||
|
||||
```python
|
||||
class OpenAIImageProvider:
|
||||
def generate(self, prompt: str, options: dict) -> str:
|
||||
response = openai.Image.create(
|
||||
model="dall-e-3",
|
||||
prompt=prompt,
|
||||
size=options.get('size', '1024x1024'),
|
||||
quality=options.get('quality', 'standard'),
|
||||
n=1
|
||||
)
|
||||
return response.data[0].url
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider: Runware
|
||||
|
||||
**Location:** `backend/igny8_core/ai/providers/runware_provider.py`
|
||||
|
||||
Alternative image generation via Runware API.
|
||||
|
||||
```python
|
||||
class RunwareImageProvider:
|
||||
def generate(self, prompt: str, options: dict) -> str:
|
||||
response = self.client.generate(
|
||||
prompt=prompt,
|
||||
width=options.get('width', 1024),
|
||||
height=options.get('height', 1024),
|
||||
model=options.get('model', 'default')
|
||||
)
|
||||
return response.image_url
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Code | Handling |
|
||||
|-------|------|----------|
|
||||
| Rate limit | `rate_limit_exceeded` | Retry with backoff |
|
||||
| Context too long | `context_length_exceeded` | Truncate input |
|
||||
| Content filter | `content_policy_violation` | Return error to user |
|
||||
| API unavailable | `api_error` | Retry or fail |
|
||||
|
||||
### Retry Strategy
|
||||
|
||||
```python
|
||||
class AIEngine:
|
||||
MAX_RETRIES = 3
|
||||
BASE_DELAY = 1 # seconds
|
||||
|
||||
def _call_with_retry(self, func, *args, **kwargs):
|
||||
for attempt in range(self.MAX_RETRIES):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except RateLimitError:
|
||||
delay = self.BASE_DELAY * (2 ** attempt)
|
||||
time.sleep(delay)
|
||||
except ContentPolicyError:
|
||||
raise # Don't retry policy violations
|
||||
raise MaxRetriesExceeded()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Per-Account Settings
|
||||
|
||||
```python
|
||||
# In AIIntegrationSettings
|
||||
openai_api_key = "sk-..."
|
||||
openai_model = "gpt-4" # or gpt-4-turbo
|
||||
image_provider = "dalle" # or "runware"
|
||||
dalle_api_key = "sk-..."
|
||||
runware_api_key = "..."
|
||||
```
|
||||
|
||||
### Model Options
|
||||
|
||||
| Model | Use Case | Token Limit |
|
||||
|-------|----------|-------------|
|
||||
| `gpt-4` | High quality content | 8,192 |
|
||||
| `gpt-4-turbo` | Faster, larger context | 128,000 |
|
||||
| `gpt-3.5-turbo` | Budget option | 4,096 |
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Logging
|
||||
|
||||
All AI operations logged with:
|
||||
- Input parameters (sanitized)
|
||||
- Output summary
|
||||
- Token usage
|
||||
- Latency
|
||||
- Error details
|
||||
|
||||
### Metrics
|
||||
|
||||
Tracked via internal logging:
|
||||
- Operations per day
|
||||
- Average latency
|
||||
- Error rate
|
||||
- Token consumption
|
||||
- Credit usage
|
||||
517
docs/90-REFERENCE/MODELS.md
Normal file
517
docs/90-REFERENCE/MODELS.md
Normal file
@@ -0,0 +1,517 @@
|
||||
# Database Models Reference
|
||||
|
||||
**Last Verified:** December 25, 2025
|
||||
|
||||
---
|
||||
|
||||
## Auth Models (`igny8_core/auth/models/`)
|
||||
|
||||
### User
|
||||
|
||||
```python
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
id = UUIDField(primary_key=True)
|
||||
email = EmailField(unique=True)
|
||||
first_name = CharField(max_length=150)
|
||||
last_name = CharField(max_length=150)
|
||||
|
||||
account = ForeignKey(Account, related_name='users')
|
||||
role = ForeignKey(Group, null=True)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
is_staff = BooleanField(default=False)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Relations:** Account (many-to-one)
|
||||
|
||||
---
|
||||
|
||||
### Account
|
||||
|
||||
```python
|
||||
class Account(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
|
||||
plan = ForeignKey(Plan, null=True)
|
||||
owner = ForeignKey(User, related_name='owned_accounts')
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Plan (many-to-one), Users (one-to-many), Sites (one-to-many)
|
||||
|
||||
---
|
||||
|
||||
### Site
|
||||
|
||||
```python
|
||||
class Site(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
domain = CharField(max_length=255, blank=True)
|
||||
|
||||
account = ForeignKey(Account, related_name='sites')
|
||||
industry = ForeignKey(Industry, null=True)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Account (many-to-one), Sectors (one-to-many), Industries (many-to-one)
|
||||
|
||||
---
|
||||
|
||||
### Sector
|
||||
|
||||
```python
|
||||
class Sector(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='sectors')
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Site (many-to-one)
|
||||
|
||||
---
|
||||
|
||||
### Industry
|
||||
|
||||
```python
|
||||
class Industry(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
```
|
||||
|
||||
**Used for:** Default seed keywords, industry-specific prompts
|
||||
|
||||
---
|
||||
|
||||
## Planner Models (`igny8_core/modules/planner/models.py`)
|
||||
|
||||
### Keyword
|
||||
|
||||
```python
|
||||
class Keyword(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
keyword = CharField(max_length=255)
|
||||
|
||||
site = ForeignKey(Site, related_name='keywords')
|
||||
sector = ForeignKey(Sector, null=True, related_name='keywords')
|
||||
cluster = ForeignKey(Cluster, null=True, related_name='keywords')
|
||||
|
||||
search_volume = IntegerField(null=True)
|
||||
difficulty = IntegerField(null=True)
|
||||
cpc = DecimalField(null=True)
|
||||
|
||||
status = CharField(choices=KEYWORD_STATUS) # pending, clustered, used, archived
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Status Values:**
|
||||
- `pending` - New, awaiting clustering
|
||||
- `clustered` - Assigned to a cluster
|
||||
- `used` - Used in content idea
|
||||
- `archived` - No longer active
|
||||
|
||||
---
|
||||
|
||||
### Cluster
|
||||
|
||||
```python
|
||||
class Cluster(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='clusters')
|
||||
sector = ForeignKey(Sector, null=True, related_name='clusters')
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Site, Sector, Keywords (one-to-many), ContentIdeas (one-to-many)
|
||||
|
||||
---
|
||||
|
||||
### ContentIdea
|
||||
|
||||
```python
|
||||
class ContentIdea(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
title = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='ideas')
|
||||
sector = ForeignKey(Sector, null=True)
|
||||
cluster = ForeignKey(Cluster, related_name='ideas')
|
||||
|
||||
primary_keyword = ForeignKey(Keyword, related_name='primary_ideas')
|
||||
secondary_keywords = ManyToManyField(Keyword, related_name='secondary_ideas')
|
||||
|
||||
status = CharField(choices=IDEA_STATUS) # pending, approved, used, archived
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writer Models (`igny8_core/modules/writer/models.py`)
|
||||
|
||||
### Task
|
||||
|
||||
```python
|
||||
class Task(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
title = CharField(max_length=255)
|
||||
brief = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='tasks')
|
||||
sector = ForeignKey(Sector, null=True)
|
||||
idea = ForeignKey(ContentIdea, null=True, related_name='tasks')
|
||||
|
||||
primary_keyword = CharField(max_length=255)
|
||||
secondary_keywords = JSONField(default=list)
|
||||
|
||||
status = CharField(choices=TASK_STATUS) # pending, in_progress, completed, cancelled
|
||||
priority = CharField(choices=PRIORITY) # low, medium, high
|
||||
|
||||
assigned_to = ForeignKey(User, null=True)
|
||||
due_date = DateField(null=True)
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Content
|
||||
|
||||
```python
|
||||
class Content(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
title = CharField(max_length=255)
|
||||
body = TextField() # HTML content
|
||||
excerpt = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='content')
|
||||
sector = ForeignKey(Sector, null=True)
|
||||
task = ForeignKey(Task, related_name='content')
|
||||
|
||||
meta_title = CharField(max_length=255, blank=True)
|
||||
meta_description = TextField(blank=True)
|
||||
|
||||
status = CharField(choices=CONTENT_STATUS) # draft, review, approved, published
|
||||
|
||||
word_count = IntegerField(default=0)
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Status Values:**
|
||||
- `draft` - Initial state after generation
|
||||
- `review` - Pending human review
|
||||
- `approved` - Ready for publishing
|
||||
- `published` - Published to WordPress
|
||||
|
||||
---
|
||||
|
||||
### ContentImage
|
||||
|
||||
```python
|
||||
class ContentImage(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
content = ForeignKey(Content, related_name='images')
|
||||
|
||||
url = URLField()
|
||||
thumbnail_url = URLField(blank=True)
|
||||
alt_text = CharField(max_length=255)
|
||||
caption = TextField(blank=True)
|
||||
|
||||
is_featured = BooleanField(default=False)
|
||||
position = IntegerField(default=0)
|
||||
|
||||
# AI generation metadata
|
||||
prompt = TextField(blank=True)
|
||||
provider = CharField(max_length=50) # dalle, runware
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Models (`igny8_core/modules/integration/models.py`)
|
||||
|
||||
### SiteIntegration
|
||||
|
||||
```python
|
||||
class SiteIntegration(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
|
||||
site = ForeignKey(Site, related_name='integrations')
|
||||
integration_type = CharField(max_length=50) # wordpress
|
||||
|
||||
# Credentials (encrypted)
|
||||
url = URLField()
|
||||
username = CharField(max_length=255)
|
||||
api_key = CharField(max_length=255) # Application password
|
||||
|
||||
# Cached structure
|
||||
categories = JSONField(default=list)
|
||||
tags = JSONField(default=list)
|
||||
authors = JSONField(default=list)
|
||||
post_types = JSONField(default=list)
|
||||
|
||||
# Status
|
||||
is_active = BooleanField(default=True)
|
||||
last_sync_at = DateTimeField(null=True)
|
||||
structure_updated_at = DateTimeField(null=True)
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Billing Models (`igny8_core/modules/billing/models.py`)
|
||||
|
||||
### Plan
|
||||
|
||||
```python
|
||||
class Plan(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=100)
|
||||
slug = SlugField(unique=True)
|
||||
|
||||
idea_credits = IntegerField(default=0)
|
||||
content_credits = IntegerField(default=0)
|
||||
image_credits = IntegerField(default=0)
|
||||
optimization_credits = IntegerField(default=0)
|
||||
|
||||
max_sites = IntegerField(default=1)
|
||||
max_users = IntegerField(default=1)
|
||||
|
||||
price_monthly = DecimalField(max_digits=10, decimal_places=2)
|
||||
price_yearly = DecimalField(max_digits=10, decimal_places=2)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
is_internal = BooleanField(default=False)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreditBalance
|
||||
|
||||
```python
|
||||
class CreditBalance(models.Model):
|
||||
account = ForeignKey(Account, related_name='credit_balances')
|
||||
site = ForeignKey(Site, null=True, related_name='credit_balances')
|
||||
|
||||
idea_credits = IntegerField(default=0)
|
||||
content_credits = IntegerField(default=0)
|
||||
image_credits = IntegerField(default=0)
|
||||
optimization_credits = IntegerField(default=0)
|
||||
|
||||
period_start = DateField()
|
||||
period_end = DateField()
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreditUsage
|
||||
|
||||
```python
|
||||
class CreditUsage(models.Model):
|
||||
account = ForeignKey(Account, related_name='credit_usage')
|
||||
site = ForeignKey(Site, null=True)
|
||||
user = ForeignKey(User)
|
||||
|
||||
credit_type = CharField(max_length=50) # idea, content, image, optimization
|
||||
amount = IntegerField()
|
||||
operation = CharField(max_length=100) # generate_content, etc.
|
||||
|
||||
related_content_type = ForeignKey(ContentType, null=True)
|
||||
related_object_id = UUIDField(null=True)
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Models (`igny8_core/modules/system/`)
|
||||
|
||||
### ModuleEnableSettings
|
||||
|
||||
```python
|
||||
class ModuleEnableSettings(models.Model):
|
||||
account = OneToOneField(Account, primary_key=True)
|
||||
|
||||
planner_enabled = BooleanField(default=True)
|
||||
writer_enabled = BooleanField(default=True)
|
||||
linker_enabled = BooleanField(default=False)
|
||||
optimizer_enabled = BooleanField(default=False)
|
||||
automation_enabled = BooleanField(default=True)
|
||||
integration_enabled = BooleanField(default=True)
|
||||
publisher_enabled = BooleanField(default=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AIIntegrationSettings
|
||||
|
||||
```python
|
||||
class AIIntegrationSettings(models.Model):
|
||||
account = ForeignKey(Account, related_name='ai_settings')
|
||||
|
||||
# OpenAI
|
||||
openai_api_key = CharField(max_length=255, blank=True)
|
||||
openai_model = CharField(max_length=50, default='gpt-4')
|
||||
|
||||
# Image generation
|
||||
image_provider = CharField(max_length=50, default='dalle') # dalle, runware
|
||||
dalle_api_key = CharField(max_length=255, blank=True)
|
||||
runware_api_key = CharField(max_length=255, blank=True)
|
||||
|
||||
is_validated = BooleanField(default=False)
|
||||
validated_at = DateTimeField(null=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PromptTemplate
|
||||
|
||||
```python
|
||||
class PromptTemplate(models.Model):
|
||||
account = ForeignKey(Account, null=True) # null = system default
|
||||
|
||||
prompt_type = CharField(max_length=100) # auto_cluster, generate_ideas, etc.
|
||||
template = TextField()
|
||||
variables = JSONField(default=list)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Publisher Models (`igny8_core/modules/publisher/models.py`)
|
||||
|
||||
### PublishingRecord
|
||||
|
||||
```python
|
||||
class PublishingRecord(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
content = ForeignKey(Content, related_name='publishing_records')
|
||||
integration = ForeignKey(SiteIntegration, related_name='publishing_records')
|
||||
|
||||
external_id = CharField(max_length=255) # WordPress post ID
|
||||
external_url = URLField(blank=True)
|
||||
|
||||
status = CharField(max_length=50) # pending, published, failed
|
||||
|
||||
published_at = DateTimeField(null=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation Models (`igny8_core/modules/automation/models.py`)
|
||||
|
||||
### AutomationConfig
|
||||
|
||||
```python
|
||||
class AutomationConfig(models.Model):
|
||||
site = ForeignKey(Site, related_name='automation_configs')
|
||||
|
||||
# Stage limits
|
||||
clustering_limit = IntegerField(default=10)
|
||||
ideas_limit = IntegerField(default=10)
|
||||
content_limit = IntegerField(default=5)
|
||||
image_limit = IntegerField(default=10)
|
||||
publish_limit = IntegerField(default=5)
|
||||
|
||||
# Timing
|
||||
delay_between_operations = IntegerField(default=5) # seconds
|
||||
max_runtime = IntegerField(default=3600) # 1 hour
|
||||
|
||||
# Behavior
|
||||
auto_approve = BooleanField(default=False)
|
||||
auto_publish = BooleanField(default=False)
|
||||
stop_on_error = BooleanField(default=True)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AutomationRun
|
||||
|
||||
```python
|
||||
class AutomationRun(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
site = ForeignKey(Site, related_name='automation_runs')
|
||||
config = ForeignKey(AutomationConfig)
|
||||
|
||||
status = CharField(max_length=50) # pending, running, paused, completed, failed, cancelled
|
||||
|
||||
# Progress tracking
|
||||
current_stage = CharField(max_length=50, blank=True)
|
||||
items_processed = IntegerField(default=0)
|
||||
items_total = IntegerField(default=0)
|
||||
|
||||
# Timing
|
||||
started_at = DateTimeField(null=True)
|
||||
completed_at = DateTimeField(null=True)
|
||||
|
||||
# Results
|
||||
error_message = TextField(blank=True)
|
||||
|
||||
started_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Relationship Overview
|
||||
|
||||
```
|
||||
Account
|
||||
├── Users (many)
|
||||
├── Sites (many)
|
||||
│ ├── Sectors (many)
|
||||
│ ├── Keywords (many)
|
||||
│ ├── Clusters (many)
|
||||
│ ├── ContentIdeas (many)
|
||||
│ ├── Tasks (many)
|
||||
│ ├── Content (many)
|
||||
│ │ └── ContentImages (many)
|
||||
│ ├── SiteIntegrations (many)
|
||||
│ │ └── PublishingRecords (many)
|
||||
│ └── AutomationConfigs (many)
|
||||
│ └── AutomationRuns (many)
|
||||
├── Plan (one)
|
||||
├── CreditBalances (many)
|
||||
├── CreditUsage (many)
|
||||
├── ModuleEnableSettings (one)
|
||||
├── AIIntegrationSettings (many)
|
||||
└── PromptTemplates (many)
|
||||
```
|
||||
Reference in New Issue
Block a user