Merge remote changes and add SEO fields to Tasks model, improve content generation response handling, and enhance progress bar animation

This commit is contained in:
Gitea Deploy
2025-11-09 21:25:11 +00:00
17 changed files with 199 additions and 1173 deletions

View File

@@ -269,6 +269,7 @@ class AICore:
'cost': cost,
'error': None,
'api_id': api_id,
'duration': request_duration, # Add duration tracking
}
else:
error_msg = 'No content in OpenAI response'
@@ -315,8 +316,9 @@ class AICore:
}
except Exception as e:
error_msg = f'Unexpected error: {str(e)}'
print(f"[AI][{function_name}][Error] {error_msg}")
logger.error(error_msg, exc_info=True)
logger.error(f"[AI][{function_name}][Error] {error_msg}", exc_info=True)
if tracker:
tracker.error('UnexpectedError', error_msg, e)
return {
'content': None,
'error': error_msg,

View File

@@ -34,16 +34,14 @@ class BaseAIFunction(ABC):
def validate(self, payload: dict, account=None) -> Dict[str, Any]:
"""
Validate input payload.
Default: checks for 'ids' array, max_items limit.
Default: checks for 'ids' array.
Override for custom validation.
"""
ids = payload.get('ids', [])
if not ids:
return {'valid': False, 'error': 'No IDs provided'}
max_items = self.get_max_items()
if max_items and len(ids) > max_items:
return {'valid': False, 'error': f'Maximum {max_items} items allowed'}
# Removed max_items limit check - no limits enforced
return {'valid': True}

View File

@@ -2,14 +2,16 @@
AI Function implementations
"""
from igny8_core.ai.functions.auto_cluster import AutoClusterFunction
from igny8_core.ai.functions.generate_ideas import GenerateIdeasFunction, generate_ideas_core
# REMOVED: generate_ideas function removed
# from igny8_core.ai.functions.generate_ideas import GenerateIdeasFunction, generate_ideas_core
from igny8_core.ai.functions.generate_content import GenerateContentFunction, generate_content_core
from igny8_core.ai.functions.generate_images import GenerateImagesFunction, generate_images_core
__all__ = [
'AutoClusterFunction',
'GenerateIdeasFunction',
'generate_ideas_core',
# REMOVED: generate_ideas function removed
# 'GenerateIdeasFunction',
# 'generate_ideas_core',
'GenerateContentFunction',
'generate_content_core',
'GenerateImagesFunction',

View File

@@ -34,14 +34,15 @@ class AutoClusterFunction(BaseAIFunction):
}
def get_max_items(self) -> int:
return 20
# No limit - return None
return None
def validate(self, payload: dict, account=None) -> Dict:
"""Custom validation for clustering with plan limit checks"""
from igny8_core.ai.validators import validate_ids, validate_keywords_exist, validate_cluster_limits
"""Custom validation for clustering"""
from igny8_core.ai.validators import validate_ids, validate_keywords_exist
# Base validation
result = validate_ids(payload, max_items=self.get_max_items())
# Base validation (no max_items limit)
result = validate_ids(payload, max_items=None)
if not result['valid']:
return result
@@ -51,10 +52,7 @@ class AutoClusterFunction(BaseAIFunction):
if not keywords_result['valid']:
return keywords_result
# Check plan limits
limit_result = validate_cluster_limits(account, operation_type='cluster')
if not limit_result['valid']:
return limit_result
# Removed plan limits check
return {'valid': True}

View File

@@ -36,7 +36,8 @@ class GenerateImagesFunction(BaseAIFunction):
}
def get_max_items(self) -> int:
return 20 # Max tasks per batch
# No limit - return None
return None
def validate(self, payload: dict, account=None) -> Dict:
"""Validate task IDs"""

View File

@@ -117,7 +117,8 @@ Make sure each prompt is detailed enough for image generation, describing the vi
# Mapping from function names to prompt types
FUNCTION_TO_PROMPT_TYPE = {
'auto_cluster': 'clustering',
'generate_ideas': 'ideas',
# REMOVED: generate_ideas function removed
# 'generate_ideas': 'ideas',
'generate_content': 'content_generation',
'generate_images': 'image_prompt_extraction',
'extract_image_prompts': 'image_prompt_extraction',

View File

@@ -66,10 +66,11 @@ def _load_auto_cluster():
from igny8_core.ai.functions.auto_cluster import AutoClusterFunction
return AutoClusterFunction
def _load_generate_ideas():
"""Lazy loader for generate_ideas function"""
from igny8_core.ai.functions.generate_ideas import GenerateIdeasFunction
return GenerateIdeasFunction
# REMOVED: generate_ideas function removed
# def _load_generate_ideas():
# """Lazy loader for generate_ideas function"""
# from igny8_core.ai.functions.generate_ideas import GenerateIdeasFunction
# return GenerateIdeasFunction
def _load_generate_content():
"""Lazy loader for generate_content function"""
@@ -82,7 +83,8 @@ def _load_generate_images():
return GenerateImagesFunction
register_lazy_function('auto_cluster', _load_auto_cluster)
register_lazy_function('generate_ideas', _load_generate_ideas)
# REMOVED: generate_ideas function removed
# register_lazy_function('generate_ideas', _load_generate_ideas)
register_lazy_function('generate_content', _load_generate_content)
register_lazy_function('generate_images', _load_generate_images)

View File

@@ -11,12 +11,13 @@ MODEL_CONFIG = {
"temperature": 0.7,
"response_format": {"type": "json_object"}, # Auto-enabled for JSON mode models
},
"generate_ideas": {
"model": "gpt-4.1",
"max_tokens": 4000,
"temperature": 0.7,
"response_format": {"type": "json_object"},
},
# REMOVED: generate_ideas function removed
# "generate_ideas": {
# "model": "gpt-4.1",
# "max_tokens": 4000,
# "temperature": 0.7,
# "response_format": {"type": "json_object"},
# },
"generate_content": {
"model": "gpt-4.1",
"max_tokens": 8000,
@@ -40,7 +41,8 @@ MODEL_CONFIG = {
FUNCTION_ALIASES = {
"cluster_keywords": "auto_cluster",
"auto_cluster_keywords": "auto_cluster",
"auto_generate_ideas": "generate_ideas",
# REMOVED: generate_ideas function removed
# "auto_generate_ideas": "generate_ideas",
"auto_generate_content": "generate_content",
"auto_generate_images": "generate_images",
}

View File

@@ -12,7 +12,8 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8.settings')
django.setup()
from igny8_core.ai.functions.auto_cluster import AutoClusterFunction
from igny8_core.ai.functions.generate_ideas import generate_ideas_core
# REMOVED: generate_ideas function removed
# from igny8_core.ai.functions.generate_ideas import generate_ideas_core
from igny8_core.ai.functions.generate_content import generate_content_core
from igny8_core.ai.functions.generate_images import generate_images_core
from igny8_core.ai.ai_core import AICore
@@ -122,7 +123,8 @@ if __name__ == '__main__':
test_ai_core()
test_json_extraction()
test_auto_cluster()
test_generate_ideas()
# REMOVED: generate_ideas function removed
# test_generate_ideas()
test_generate_content()
test_generate_images()

View File

@@ -236,21 +236,40 @@ class ConsoleStepTracker:
self.start_time = time.time()
self.steps = []
self.current_phase = None
# Debug: Verify DEBUG_MODE is enabled
import sys
if DEBUG_MODE:
init_msg = f"[DEBUG] ConsoleStepTracker initialized for '{function_name}' - DEBUG_MODE is ENABLED"
logger.info(init_msg)
print(init_msg, flush=True, file=sys.stdout)
else:
init_msg = f"[WARNING] ConsoleStepTracker initialized for '{function_name}' - DEBUG_MODE is DISABLED"
logger.warning(init_msg)
print(init_msg, flush=True, file=sys.stdout)
def _log(self, phase: str, message: str, status: str = 'info'):
"""Internal logging method that checks DEBUG_MODE"""
if not DEBUG_MODE:
return
import sys
timestamp = datetime.now().strftime('%H:%M:%S')
phase_label = phase.upper()
if status == 'error':
print(f"[{timestamp}] [{self.function_name}] [{phase_label}] [ERROR] {message}")
log_msg = f"[{timestamp}] [{self.function_name}] [{phase_label}] [ERROR] {message}"
# Use logger.error for errors so they're always visible
logger.error(log_msg)
elif status == 'success':
print(f"[{timestamp}] [{self.function_name}] [{phase_label}] ✅ {message}")
log_msg = f"[{timestamp}] [{self.function_name}] [{phase_label}] ✅ {message}"
logger.info(log_msg)
else:
print(f"[{timestamp}] [{self.function_name}] [{phase_label}] {message}")
log_msg = f"[{timestamp}] [{self.function_name}] [{phase_label}] {message}"
logger.info(log_msg)
# Also print to stdout for immediate visibility (works in Celery worker logs)
print(log_msg, flush=True, file=sys.stdout)
self.steps.append({
'timestamp': timestamp,
@@ -285,7 +304,10 @@ class ConsoleStepTracker:
duration = time.time() - self.start_time
self._log('DONE', f"{message} (Duration: {duration:.2f}s)", status='success')
if DEBUG_MODE:
print(f"[{self.function_name}] === AI Task Complete ===")
import sys
complete_msg = f"[{self.function_name}] === AI Task Complete ==="
logger.info(complete_msg)
print(complete_msg, flush=True, file=sys.stdout)
def error(self, error_type: str, message: str, exception: Exception = None):
"""Log error with standardized format"""
@@ -294,9 +316,12 @@ class ConsoleStepTracker:
error_msg += f" ({type(exception).__name__})"
self._log(self.current_phase or 'ERROR', error_msg, status='error')
if DEBUG_MODE and exception:
import sys
import traceback
print(f"[{self.function_name}] [ERROR] Stack trace:")
traceback.print_exc()
error_trace_msg = f"[{self.function_name}] [ERROR] Stack trace:"
logger.error(error_trace_msg, exc_info=exception)
print(error_trace_msg, flush=True, file=sys.stdout)
traceback.print_exc(file=sys.stdout)
def retry(self, attempt: int, max_attempts: int, reason: str = ""):
"""Log retry attempt"""

View File

@@ -10,11 +10,11 @@ logger = logging.getLogger(__name__)
def validate_ids(payload: dict, max_items: Optional[int] = None) -> Dict[str, Any]:
"""
Base validation: checks for 'ids' array and max_items limit.
Base validation: checks for 'ids' array.
Args:
payload: Request payload containing 'ids' array
max_items: Maximum number of items allowed (None = no limit)
max_items: Maximum number of items allowed (deprecated - no longer enforced)
Returns:
Dict with 'valid' (bool) and optional 'error' (str)
@@ -23,8 +23,7 @@ def validate_ids(payload: dict, max_items: Optional[int] = None) -> Dict[str, An
if not ids:
return {'valid': False, 'error': 'No IDs provided'}
if max_items and len(ids) > max_items:
return {'valid': False, 'error': f'Maximum {max_items} items allowed'}
# Removed max_items limit check - no limits enforced
return {'valid': True}
@@ -55,46 +54,16 @@ def validate_keywords_exist(ids: list, account=None) -> Dict[str, Any]:
def validate_cluster_limits(account, operation_type: str = 'cluster') -> Dict[str, Any]:
"""
Validate plan limits for cluster operations.
DISABLED: All limits have been removed.
Args:
account: Account object
operation_type: Type of operation ('cluster', 'idea', etc.)
Returns:
Dict with 'valid' (bool) and optional 'error' (str)
Dict with 'valid' (bool) - always returns valid
"""
if not account:
return {'valid': False, 'error': 'Account is required'}
plan = getattr(account, 'plan', None)
if not plan:
return {'valid': False, 'error': 'Account does not have an active plan'}
if operation_type == 'cluster':
from igny8_core.modules.planner.models import Clusters
# Check daily cluster limit
now = timezone.now()
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
clusters_today = Clusters.objects.filter(
account=account,
created_at__gte=start_of_day
).count()
if plan.daily_cluster_limit and clusters_today >= plan.daily_cluster_limit:
return {
'valid': False,
'error': f'Daily cluster limit reached ({plan.daily_cluster_limit} clusters per day). Please try again tomorrow.'
}
# Check max clusters limit
total_clusters = Clusters.objects.filter(account=account).count()
if plan.max_clusters and total_clusters >= plan.max_clusters:
return {
'valid': False,
'error': f'Maximum cluster limit reached ({plan.max_clusters} clusters). Please upgrade your plan or delete existing clusters.'
}
# All limits removed - always return valid
return {'valid': True}