263 lines
10 KiB
Python
263 lines
10 KiB
Python
"""
|
|
System module models - for global settings and prompts
|
|
"""
|
|
from django.db import models
|
|
from igny8_core.auth.models import AccountBaseModel
|
|
|
|
# Import settings models
|
|
from .settings_models import (
|
|
SystemSettings, AccountSettings, UserSettings, ModuleSettings, ModuleEnableSettings, AISettings
|
|
)
|
|
|
|
|
|
class AIPrompt(AccountBaseModel):
|
|
"""
|
|
Account-specific AI Prompt templates.
|
|
Stores global default in default_prompt, current value in prompt_value.
|
|
When user saves an override, prompt_value changes but default_prompt stays.
|
|
Reset copies default_prompt back to prompt_value.
|
|
"""
|
|
|
|
PROMPT_TYPE_CHOICES = [
|
|
('clustering', 'Clustering'),
|
|
('ideas', 'Ideas Generation'),
|
|
('content_generation', 'Content Generation'),
|
|
('image_prompt_extraction', 'Image Prompt Extraction'),
|
|
('image_prompt_template', 'Image Prompt Template'),
|
|
('negative_prompt', 'Negative Prompt'),
|
|
('site_structure_generation', 'Site Structure Generation'), # Phase 7: Site Builder prompts
|
|
# Phase 8: Universal Content Types
|
|
('product_generation', 'Product Content Generation'),
|
|
('service_generation', 'Service Page Generation'),
|
|
('taxonomy_generation', 'Taxonomy Generation'),
|
|
]
|
|
|
|
prompt_type = models.CharField(max_length=50, choices=PROMPT_TYPE_CHOICES, db_index=True)
|
|
prompt_value = models.TextField(help_text="Current prompt text (customized or default)")
|
|
default_prompt = models.TextField(
|
|
blank=True,
|
|
help_text="Global default prompt - used for reset to default"
|
|
)
|
|
is_customized = models.BooleanField(
|
|
default=False,
|
|
help_text="True if account customized the prompt, False if using global default"
|
|
)
|
|
is_active = models.BooleanField(default=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'igny8_ai_prompts'
|
|
ordering = ['prompt_type']
|
|
unique_together = [['account', 'prompt_type']] # Each account can have one prompt per type
|
|
indexes = [
|
|
models.Index(fields=['prompt_type']),
|
|
models.Index(fields=['account', 'prompt_type']),
|
|
models.Index(fields=['is_customized']),
|
|
]
|
|
|
|
@classmethod
|
|
def get_effective_prompt(cls, account, prompt_type):
|
|
"""
|
|
Get the effective prompt for an account.
|
|
Returns account-specific prompt if exists and customized.
|
|
Otherwise returns global default.
|
|
"""
|
|
from .global_settings_models import GlobalAIPrompt
|
|
|
|
# Try to get account-specific prompt
|
|
try:
|
|
account_prompt = cls.objects.get(account=account, prompt_type=prompt_type, is_active=True)
|
|
# If customized, use account's version
|
|
if account_prompt.is_customized:
|
|
return account_prompt.prompt_value
|
|
# If not customized, use default_prompt from account record or global
|
|
return account_prompt.default_prompt or account_prompt.prompt_value
|
|
except cls.DoesNotExist:
|
|
pass
|
|
|
|
# Fallback to global prompt
|
|
try:
|
|
global_prompt = GlobalAIPrompt.objects.get(prompt_type=prompt_type, is_active=True)
|
|
return global_prompt.prompt_value
|
|
except GlobalAIPrompt.DoesNotExist:
|
|
return None
|
|
|
|
def reset_to_default(self):
|
|
"""Reset prompt to global default from GlobalAIPrompt"""
|
|
from .global_settings_models import GlobalAIPrompt
|
|
|
|
try:
|
|
global_prompt = GlobalAIPrompt.objects.get(
|
|
prompt_type=self.prompt_type,
|
|
is_active=True
|
|
)
|
|
self.prompt_value = global_prompt.prompt_value
|
|
self.default_prompt = global_prompt.prompt_value
|
|
self.is_customized = False
|
|
self.save()
|
|
except GlobalAIPrompt.DoesNotExist:
|
|
raise ValueError(
|
|
f"Cannot reset: Global prompt '{self.prompt_type}' not found. "
|
|
f"Please configure it in Django admin at: /admin/system/globalaiprompt/"
|
|
)
|
|
|
|
def __str__(self):
|
|
status = "Custom" if self.is_customized else "Default"
|
|
return f"{self.get_prompt_type_display()} ({status})"
|
|
|
|
|
|
class IntegrationSettings(AccountBaseModel):
|
|
"""
|
|
Per-account integration settings overrides.
|
|
|
|
IMPORTANT: This model stores ONLY model selection and parameter overrides.
|
|
API keys are NEVER stored here - they come from GlobalIntegrationSettings.
|
|
|
|
Free plan: Cannot create overrides, must use global defaults
|
|
Starter/Growth/Scale plans: Can override model, temperature, tokens, image settings
|
|
|
|
NULL values in config mean "use global default"
|
|
"""
|
|
|
|
INTEGRATION_TYPE_CHOICES = [
|
|
('openai', 'OpenAI'),
|
|
('dalle', 'DALL-E'),
|
|
('anthropic', 'Anthropic'),
|
|
('runware', 'Runware'),
|
|
]
|
|
|
|
integration_type = models.CharField(max_length=50, choices=INTEGRATION_TYPE_CHOICES, db_index=True)
|
|
config = models.JSONField(
|
|
default=dict,
|
|
help_text=(
|
|
"Model and parameter overrides only. Fields: model, temperature, max_tokens, "
|
|
"image_size, image_quality, etc. NULL = use global default. "
|
|
"NEVER store API keys here."
|
|
)
|
|
)
|
|
is_active = models.BooleanField(default=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'igny8_integration_settings'
|
|
unique_together = [['account', 'integration_type']]
|
|
ordering = ['integration_type']
|
|
indexes = [
|
|
models.Index(fields=['integration_type']),
|
|
models.Index(fields=['account', 'integration_type']),
|
|
]
|
|
|
|
def __str__(self):
|
|
account = getattr(self, 'account', None)
|
|
return f"{self.get_integration_type_display()} - {account.name if account else 'No Account'}"
|
|
|
|
|
|
class AuthorProfile(AccountBaseModel):
|
|
"""
|
|
Writing style profiles - tone, language, structure templates.
|
|
Can be cloned from global templates or created from scratch.
|
|
Examples: "SaaS B2B Informative", "E-commerce Product Descriptions", etc.
|
|
"""
|
|
name = models.CharField(max_length=255, help_text="Profile name (e.g., 'SaaS B2B Informative')")
|
|
description = models.TextField(blank=True, help_text="Description of the writing style")
|
|
tone = models.CharField(
|
|
max_length=100,
|
|
help_text="Writing tone (e.g., 'Professional', 'Casual', 'Technical', 'Conversational')"
|
|
)
|
|
language = models.CharField(max_length=50, default='en', help_text="Language code (e.g., 'en', 'es', 'fr')")
|
|
structure_template = models.JSONField(
|
|
default=dict,
|
|
help_text="Structure template defining content sections and their order"
|
|
)
|
|
is_custom = models.BooleanField(
|
|
default=False,
|
|
help_text="True if created by account, False if cloned from global template"
|
|
)
|
|
cloned_from = models.ForeignKey(
|
|
'system.GlobalAuthorProfile',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='cloned_instances',
|
|
help_text="Reference to the global template this was cloned from"
|
|
)
|
|
is_active = models.BooleanField(default=True, db_index=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'igny8_author_profiles'
|
|
ordering = ['name']
|
|
verbose_name = 'Author Profile'
|
|
verbose_name_plural = 'Author Profiles'
|
|
indexes = [
|
|
models.Index(fields=['account', 'is_active']),
|
|
models.Index(fields=['name']),
|
|
models.Index(fields=['is_custom']),
|
|
]
|
|
|
|
def __str__(self):
|
|
account = getattr(self, 'account', None)
|
|
status = "Custom" if self.is_custom else "Template"
|
|
return f"{self.name} ({status}) - {account.name if account else 'No Account'}"
|
|
|
|
|
|
class Strategy(AccountBaseModel):
|
|
"""
|
|
Defined content strategies per sector, integrating prompt types, section logic, etc.
|
|
Can be cloned from global templates or created from scratch.
|
|
Links together prompts, author profiles, and sector-specific content strategies.
|
|
"""
|
|
name = models.CharField(max_length=255, help_text="Strategy name")
|
|
description = models.TextField(blank=True, help_text="Description of the content strategy")
|
|
sector = models.ForeignKey(
|
|
'igny8_core_auth.Sector',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='strategies',
|
|
help_text="Optional: Link strategy to a specific sector"
|
|
)
|
|
prompt_types = models.JSONField(
|
|
default=list,
|
|
help_text="List of prompt types to use (e.g., ['clustering', 'ideas', 'content_generation'])"
|
|
)
|
|
section_logic = models.JSONField(
|
|
default=dict,
|
|
help_text="Section logic configuration defining content structure and flow"
|
|
)
|
|
is_custom = models.BooleanField(
|
|
default=False,
|
|
help_text="True if created by account, False if cloned from global template"
|
|
)
|
|
cloned_from = models.ForeignKey(
|
|
'system.GlobalStrategy',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='cloned_instances',
|
|
help_text="Reference to the global template this was cloned from"
|
|
)
|
|
is_active = models.BooleanField(default=True, db_index=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'igny8_strategies'
|
|
ordering = ['name']
|
|
verbose_name = 'Strategy'
|
|
verbose_name_plural = 'Strategies'
|
|
indexes = [
|
|
models.Index(fields=['account', 'is_active']),
|
|
models.Index(fields=['account', 'sector']),
|
|
models.Index(fields=['name']),
|
|
models.Index(fields=['is_custom']),
|
|
]
|
|
|
|
def __str__(self):
|
|
sector_name = self.sector.name if self.sector else 'Global'
|
|
status = "Custom" if self.is_custom else "Template"
|
|
return f"{self.name} ({status}) - {sector_name}"
|