Resolve merge conflict in authStore.ts - use dynamic import for fetchAPI

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-16 13:47:10 +00:00
24 changed files with 2374 additions and 5411 deletions

View File

@@ -26,6 +26,17 @@ Each entry follows this format:
## [Unreleased] ## [Unreleased]
### Changed
- **API Documentation Consolidation**: Consolidated all API documentation into single comprehensive reference
- Created `docs/API-COMPLETE-REFERENCE.md` - Unified API documentation covering all endpoints, authentication, response formats, error handling, rate limiting, permissions, and integration examples
- Removed redundant documentation files:
- `docs/API-DOCUMENTATION.md` (consolidated into complete reference)
- `docs/DOCUMENTATION-SUMMARY.md` (consolidated into complete reference)
- `unified-api/API-ENDPOINTS-ANALYSIS.md` (consolidated into complete reference)
- `unified-api/API-STANDARD-v1.0.md` (consolidated into complete reference)
- New unified document includes: complete endpoint reference, authentication guide, response format standards, error handling, rate limiting, pagination, roles & permissions, tenant/site/sector scoping, integration examples (Python, JavaScript, cURL, PHP), testing & debugging, and change management
- **Impact**: Single source of truth for all API documentation, easier to maintain and navigate
### Added ### Added
- Unified API Standard v1.0 implementation - Unified API Standard v1.0 implementation
- API Monitor page for endpoint health monitoring - API Monitor page for endpoint health monitoring
@@ -48,6 +59,15 @@ Each entry follows this format:
- **API Documentation**: Updated Swagger/ReDoc description to include all public endpoints - **API Documentation**: Updated Swagger/ReDoc description to include all public endpoints
- Added `/api/v1/system/ping/` to public endpoints list - Added `/api/v1/system/ping/` to public endpoints list
- Updated schema extensions to properly tag ping endpoint - Updated schema extensions to properly tag ping endpoint
- **AI Framework Refactoring**: Removed hardcoded model defaults, IntegrationSettings is now the single source of truth
- Removed `MODEL_CONFIG` dictionary with hardcoded defaults
- Removed Django settings `DEFAULT_AI_MODEL` fallback
- `get_model_config()` now requires `account` parameter and raises clear errors if IntegrationSettings not configured
- All AI functions now require account-specific model configuration
- Removed orphan code: `get_model()`, `get_max_tokens()`, `get_temperature()` helper functions
- Removed unused exports from `__init__.py`: `register_function`, `list_functions`, `get_model`, `get_max_tokens`, `get_temperature`
- **Impact**: Each account must configure their own AI models in IntegrationSettings
- **Documentation**: See `backend/igny8_core/ai/REFACTORING-IMPLEMENTED.md` for complete details
### Fixed ### Fixed
- Keyword edit form now correctly populates existing values - Keyword edit form now correctly populates existing values
@@ -59,10 +79,22 @@ Each entry follows this format:
- `generate_image()`, `create()`, `save_settings()` methods now use unified format - `generate_image()`, `create()`, `save_settings()` methods now use unified format
- `get_image_generation_settings()` and `task_progress()` methods now use unified format - `get_image_generation_settings()` and `task_progress()` methods now use unified format
- All error responses now include `request_id` and follow unified format - All error responses now include `request_id` and follow unified format
- Fixed OpenAI integration endpoint error handling - invalid API keys now return 400 (Bad Request) instead of 401 (Unauthorized)
- **Frontend Components**: Updated to work with unified format - **Frontend Components**: Updated to work with unified format
- `ValidationCard.tsx` - Removed dual-format handling, now works with extracted data - `ValidationCard.tsx` - Removed dual-format handling, now works with extracted data
- `Integration.tsx` - Simplified to work with unified format - `Integration.tsx` - Simplified to work with unified format
- `ImageGenerationCard.tsx` - Updated to work with extracted data format - `ImageGenerationCard.tsx` - Updated to work with extracted data format
- **Frontend Authentication**: Fixed `getAuthToken is not defined` error in `authStore.ts`
- Updated `refreshUser()` to use `fetchAPI()` instead of manual fetch with `getAuthToken()`
- Removed error throwing from catch block to prevent error accumulation
- **Frontend Error Handling**: Fixed console error accumulation
- `ResourceDebugOverlay.tsx` now silently ignores 404 errors for request-metrics endpoint
- Removed error throwing from `refreshUser()` catch block to prevent error spam
- **AI Framework Error Handling**: Improved error messages and exception handling
- `AIEngine._handle_error()` now preserves exception types for better error messages
- All AI function errors now include proper `error_type` (ConfigurationError, AccountNotFound, etc.)
- Fixed "Task failed - exception details unavailable" by improving error type preservation
- Error messages now clearly indicate when IntegrationSettings are missing or misconfigured
--- ---
@@ -332,7 +364,7 @@ Each entry follows this format:
- Added custom authentication extensions for JWT Bearer tokens - Added custom authentication extensions for JWT Bearer tokens
- **Comprehensive Documentation Files** - **Comprehensive Documentation Files**
- `docs/API-DOCUMENTATION.md` - Complete API reference with examples - `docs/API-COMPLETE-REFERENCE.md` - Complete unified API reference (consolidated from multiple files)
- Quick start guide - Quick start guide
- Endpoint reference - Endpoint reference
- Code examples (Python, JavaScript, cURL) - Code examples (Python, JavaScript, cURL)

View File

@@ -1,820 +0,0 @@
# Planner & Writer Modules - Comprehensive Audit Report
**Date:** 2025-01-XX
**Scope:** Complete audit of Planner and Writer modules including pages, filters, forms, CRUD operations, bulk operations, import/export, and AI functions
**Reference Documentation:** `docs/06-FUNCTIONAL-BUSINESS-LOGIC.md`, `docs/unified-api/API-STANDARD-v1.0.md`
---
## Executive Summary
### Overall Health: **85% Complete**
**Strengths:**
- ✅ Core CRUD operations fully implemented across all pages
- ✅ AI functions properly integrated with unified framework
- ✅ Bulk operations implemented for all major entities
- ✅ Unified API response format compliance (80-85%)
- ✅ Comprehensive filtering and search capabilities
- ✅ Import/Export functionality for Keywords
**Critical Gaps:**
- ❌ Missing permission classes on ViewSets (security risk)
- ❌ Export functionality missing for Clusters, Ideas, Tasks, Content, Images
- ❌ Import functionality missing for Clusters, Ideas, Tasks, Content
- ❌ Base ViewSet `list()` method not overridden (inconsistent responses)
- ❌ Some filters documented but not implemented in frontend
**Moderate Gaps:**
- ⚠️ Missing difficulty range filter UI for Clusters (backend supports it)
- ⚠️ Missing volume range filter UI for Ideas (not documented but would be useful)
- ⚠️ Content page missing bulk operations (delete, update status)
- ⚠️ Images page missing export functionality
---
## 1. PLANNER MODULE AUDIT
### 1.1 Keywords Page (`/planner/keywords`)
#### ✅ **Fully Implemented**
**Backend (`KeywordViewSet`):**
- ✅ CRUD operations (create, read, update, delete)
- ✅ List with pagination (`CustomPageNumberPagination`)
- ✅ Unified response format (`success_response`, `error_response`)
- ✅ Filtering: `status`, `cluster_id`, `seed_keyword__intent`, `seed_keyword_id`
- ✅ Search: `seed_keyword__keyword`
- ✅ Ordering: `created_at`, `seed_keyword__volume`, `seed_keyword__difficulty`
- ✅ Custom filters: `difficulty_min`, `difficulty_max`, `volume_min`, `volume_max`
- ✅ Bulk delete (`bulk_delete`)
- ✅ Bulk update status (`bulk_update`)
- ✅ Bulk add from seed (`bulk_add_from_seed`)
- ✅ Export CSV (`export`) - supports filtered export and selected IDs
- ✅ Import CSV (`import_keywords`)
- ✅ AI clustering (`auto_cluster`) - unified framework
- ✅ Rate throttling (`throttle_scope: 'planner'`)
- ✅ Site/Sector filtering (inherited from `SiteSectorModelViewSet`)
**Frontend (`Keywords.tsx`):**
- ✅ Table with pagination
- ✅ Filters: search, status, intent, difficulty, cluster, volume range
- ✅ Sorting by multiple columns
- ✅ Create/Edit form modal
- ✅ Delete confirmation
- ✅ Bulk selection and operations
- ✅ Import CSV button and functionality
- ✅ Export CSV button and functionality
- ✅ Auto Cluster AI function with progress modal
- ✅ Bulk add from seed keywords
- ✅ Resource Debug logs for AI functions
#### ❌ **Gaps**
**Backend:**
-`permission_classes = []` - **CRITICAL SECURITY GAP** - Should use `IsAuthenticatedAndActive` and `HasTenantAccess`
-`list()` method override exists but doesn't use base class pattern consistently
**Frontend:**
- ✅ All documented features implemented
**Documentation Compliance:** 95% (missing permission classes)
---
### 1.2 Clusters Page (`/planner/clusters`)
#### ✅ **Fully Implemented**
**Backend (`ClusterViewSet`):**
- ✅ CRUD operations
- ✅ List with pagination
- ✅ Unified response format
- ✅ Filtering: `status`
- ✅ Search: `name`
- ✅ Ordering: `name`, `created_at`, `keywords_count`, `volume`, `difficulty`
- ✅ Custom filters: `volume_min`, `volume_max`, `difficulty_min`, `difficulty_max` (via annotations)
- ✅ Bulk delete (`bulk_delete`)
- ✅ AI idea generation (`auto_generate_ideas`) - unified framework
- ✅ Optimized keyword stats calculation (`prefetch_keyword_stats`)
- ✅ Rate throttling
- ✅ Site/Sector filtering
**Frontend (`Clusters.tsx`):**
- ✅ Table with pagination
- ✅ Filters: search, status, volume range, difficulty range
- ✅ Sorting
- ✅ Create/Edit form modal
- ✅ Delete confirmation
- ✅ Bulk selection and delete
- ✅ Auto Generate Ideas AI function with progress modal
- ✅ Resource Debug logs
#### ❌ **Gaps**
**Backend:**
- ❌ Missing `permission_classes` - **CRITICAL SECURITY GAP**
- ❌ Missing export functionality (documented but not implemented)
- ❌ Missing bulk update status (would be useful)
**Frontend:**
- ❌ Missing export CSV button/functionality
- ⚠️ Difficulty range filter exists but UI could be improved (uses dropdown instead of range slider)
**Documentation Compliance:** 85% (missing export, permission classes)
---
### 1.3 Ideas Page (`/planner/ideas`)
#### ✅ **Fully Implemented**
**Backend (`ContentIdeasViewSet`):**
- ✅ CRUD operations
- ✅ List with pagination
- ✅ Unified response format
- ✅ Filtering: `status`, `keyword_cluster_id`, `content_structure`, `content_type`
- ✅ Search: `idea_title`
- ✅ Ordering: `idea_title`, `created_at`, `estimated_word_count`
- ✅ Bulk delete (`bulk_delete`)
- ✅ Bulk queue to writer (`bulk_queue_to_writer`) - creates Tasks
- ✅ Rate throttling
- ✅ Site/Sector filtering
**Frontend (`Ideas.tsx`):**
- ✅ Table with pagination
- ✅ Filters: search, status, cluster, structure, type
- ✅ Sorting
- ✅ Create/Edit form modal
- ✅ Delete confirmation
- ✅ Bulk selection and delete
- ✅ Bulk queue to writer action
- ✅ Resource Debug logs
#### ❌ **Gaps**
**Backend:**
- ❌ Missing `permission_classes` - **CRITICAL SECURITY GAP**
- ❌ Missing export functionality (not documented but would be useful)
- ❌ Missing bulk update status (would be useful)
**Frontend:**
- ❌ Missing export CSV button/functionality
- ⚠️ No volume/difficulty filters (not in documentation, but could be useful for prioritization)
**Documentation Compliance:** 90% (missing permission classes, export would be nice-to-have)
---
### 1.4 Keyword Opportunities Page (`/planner/keyword-opportunities`)
#### ✅ **Fully Implemented**
**Backend:**
- Uses `SeedKeyword` model (auth module)
- Filtering and search implemented
**Frontend (`KeywordOpportunities.tsx`):**
- ✅ Table with pagination
- ✅ Filters: search, intent, difficulty
- ✅ Sorting
- ✅ Bulk add to keywords workflow
- ✅ Individual add to keywords
**Documentation Compliance:** 100% (this page is for discovery, not management)
---
## 2. WRITER MODULE AUDIT
### 2.1 Tasks Page (`/writer/tasks`)
#### ✅ **Fully Implemented**
**Backend (`TasksViewSet`):**
- ✅ CRUD operations
- ✅ List with pagination
- ✅ Unified response format
- ✅ Filtering: `status`, `cluster_id`, `content_type`, `content_structure`
- ✅ Search: `title`, `keywords`
- ✅ Ordering: `title`, `created_at`, `word_count`, `status`
- ✅ Bulk delete (`bulk_delete`)
- ✅ Bulk update status (`bulk_update`)
- ✅ AI content generation (`auto_generate_content`) - unified framework with comprehensive error handling
- ✅ Rate throttling (`throttle_scope: 'writer'`)
- ✅ Site/Sector filtering
- ✅ Content record relationship (select_related optimization)
**Frontend (`Tasks.tsx`):**
- ✅ Table with pagination
- ✅ Filters: search, status, cluster, structure, type
- ✅ Sorting
- ✅ Create/Edit form modal
- ✅ Delete confirmation
- ✅ Bulk selection and operations
- ✅ Auto Generate Content AI function with progress modal
- ✅ Resource Debug logs
- ✅ Content preview integration
#### ❌ **Gaps**
**Backend:**
- ❌ Missing `permission_classes` - **CRITICAL SECURITY GAP**
- ❌ Missing export functionality (not documented but would be useful)
- ❌ Missing import functionality (not documented but would be useful)
**Frontend:**
- ❌ Missing export CSV button/functionality
- ❌ Missing import CSV button/functionality
**Documentation Compliance:** 90% (missing permission classes, export/import would be nice-to-have)
---
### 2.2 Content Page (`/writer/content`)
#### ✅ **Fully Implemented**
**Backend (`ContentViewSet`):**
- ✅ CRUD operations
- ✅ List with pagination
- ✅ Unified response format
- ✅ Filtering: `task_id`, `status`
- ✅ Search: `title`, `meta_title`, `primary_keyword`
- ✅ Ordering: `generated_at`, `updated_at`, `word_count`, `status`
- ✅ AI image prompt generation (`generate_image_prompts`) - unified framework
- ✅ Rate throttling
- ✅ Site/Sector filtering
- ✅ Helper fields: `has_image_prompts`, `has_generated_images`
**Frontend (`Content.tsx`):**
- ✅ Table with pagination
- ✅ Filters: search, status
- ✅ Sorting
- ✅ Content detail view (via ContentView page)
- ✅ Generate Image Prompts AI function with progress modal
- ✅ Resource Debug logs
#### ❌ **Gaps**
**Backend:**
- ❌ Missing `permission_classes` - **CRITICAL SECURITY GAP**
- ❌ Missing bulk delete (would be useful)
- ❌ Missing bulk update status (would be useful)
- ❌ Missing export functionality (not documented but would be useful)
**Frontend:**
- ❌ Missing bulk selection and operations
- ❌ Missing export CSV button/functionality
- ❌ Missing edit form (content editing done in ContentView page, but no inline edit)
**Documentation Compliance:** 75% (missing bulk operations, permission classes)
---
### 2.3 Images Page (`/writer/images`)
#### ✅ **Fully Implemented**
**Backend (`ImagesViewSet`):**
- ✅ CRUD operations
- ✅ List with pagination
- ✅ Unified response format
- ✅ Filtering: `task_id`, `content_id`, `image_type`, `status`
- ✅ Ordering: `created_at`, `position`, `id`
- ✅ Bulk update status (`bulk_update`) - supports content_id or image IDs
- ✅ AI image generation (`auto_generate`, `generate_images`) - unified framework
- ✅ Content images grouped endpoint (`content_images`) - returns grouped by content
- ✅ Image file serving (`serve_image_file`) - serves local files
- ✅ Rate throttling
- ✅ Site/Sector filtering
**Frontend (`Images.tsx`):**
- ✅ Table with grouped content images (one row per content)
- ✅ Filters: search, status
- ✅ Sorting
- ✅ Image queue modal for generation
- ✅ Single record status update modal
- ✅ Image preview modal
- ✅ Generate Images AI function with progress modal
- ✅ Resource Debug logs
- ✅ Image generation settings integration
#### ❌ **Gaps**
**Backend:**
- ❌ Missing `permission_classes` - **CRITICAL SECURITY GAP**
- ❌ Missing export functionality (not documented but would be useful)
- ❌ Missing bulk delete (would be useful)
**Frontend:**
- ❌ Missing export CSV button/functionality
- ❌ Missing bulk delete action
**Documentation Compliance:** 85% (missing permission classes, export/bulk delete would be nice-to-have)
---
### 2.4 Published & Drafts Pages
#### ✅ **Fully Implemented**
**Backend:**
- Uses same `ContentViewSet` with status filtering
**Frontend:**
- ✅ Published page: filtered view of published content
- ✅ Drafts page: filtered view of draft content
- ✅ Content detail view integration
**Documentation Compliance:** 100%
---
## 3. AI FUNCTIONS AUDIT
### 3.1 Planner AI Functions
#### ✅ **auto_cluster** (Keywords → Auto Cluster)
**Backend Implementation:**
- ✅ Endpoint: `POST /v1/planner/keywords/auto_cluster/`
- ✅ Uses unified AI framework (`run_ai_task` with `function_name='auto_cluster'`)
- ✅ Validates input (max 20 keywords)
- ✅ Queues Celery task with fallback to synchronous execution
- ✅ Returns task_id for progress tracking
- ✅ Proper error handling and logging
- ✅ Account ID passed for credit deduction
**Frontend Implementation:**
- ✅ Progress modal with polling
- ✅ Resource Debug logs
- ✅ Error handling and user feedback
- ✅ Auto-reload on completion
**Documentation Compliance:** 100% ✅
---
#### ✅ **auto_generate_ideas** (Clusters → Auto Generate Ideas)
**Backend Implementation:**
- ✅ Endpoint: `POST /v1/planner/clusters/auto_generate_ideas/`
- ✅ Uses unified AI framework (`run_ai_task` with `function_name='auto_generate_ideas'`)
- ✅ Validates input (max 10 clusters)
- ✅ Queues Celery task with fallback
- ✅ Returns task_id for progress tracking
- ✅ Proper error handling
**Frontend Implementation:**
- ✅ Progress modal with polling
- ✅ Resource Debug logs
- ✅ Error handling
**Documentation Compliance:** 100% ✅
**Note:** Documentation says function is `generate_ideas` but implementation uses `auto_generate_ideas` - this is fine, just a naming difference.
---
### 3.2 Writer AI Functions
#### ✅ **auto_generate_content** (Tasks → Generate Content)
**Backend Implementation:**
- ✅ Endpoint: `POST /v1/writer/tasks/auto_generate_content/`
- ✅ Uses unified AI framework (`run_ai_task` with `function_name='generate_content'`)
- ✅ Validates input (max 10 tasks)
- ✅ Comprehensive error handling (database errors, Celery errors, validation errors)
- ✅ Queues Celery task with fallback
- ✅ Returns task_id for progress tracking
- ✅ Detailed logging for debugging
**Frontend Implementation:**
- ✅ Progress modal with polling
- ✅ Resource Debug logs
- ✅ Error handling
- ✅ Auto-reload on completion
**Documentation Compliance:** 100% ✅
**Note:** Documentation says max 50 tasks, implementation allows max 10 - this is a reasonable limit.
---
#### ✅ **generate_image_prompts** (Content → Generate Image Prompts)
**Backend Implementation:**
- ✅ Endpoint: `POST /v1/writer/content/generate_image_prompts/`
- ✅ Uses unified AI framework (`run_ai_task` with `function_name='generate_image_prompts'`)
- ✅ Validates input (requires IDs)
- ✅ Queues Celery task with fallback
- ✅ Returns task_id for progress tracking
**Frontend Implementation:**
- ✅ Progress modal with polling
- ✅ Resource Debug logs
- ✅ Error handling
**Documentation Compliance:** 100% ✅
---
#### ✅ **generate_images** (Images → Generate Images)
**Backend Implementation:**
- ✅ Endpoint: `POST /v1/writer/images/generate_images/`
- ✅ Uses unified AI framework (`process_image_generation_queue` - specialized for sequential processing)
- ✅ Validates input (requires image IDs)
- ✅ Queues Celery task
- ✅ Returns task_id for progress tracking
- ✅ Supports content_id for batch operations
**Frontend Implementation:**
- ✅ Image queue modal
- ✅ Progress tracking
- ✅ Resource Debug logs
- ✅ Error handling
**Documentation Compliance:** 100% ✅
---
#### ✅ **auto_generate** (Images → Auto Generate - Legacy)
**Backend Implementation:**
- ✅ Endpoint: `POST /v1/writer/images/auto_generate/`
- ✅ Uses unified AI framework (`run_ai_task` with `function_name='generate_images'`)
- ✅ Validates input (max 10 tasks)
- ✅ Queues Celery task with fallback
**Note:** This appears to be a legacy endpoint. The `generate_images` endpoint is the preferred one.
**Documentation Compliance:** 95% (legacy endpoint, but functional)
---
## 4. API STANDARD COMPLIANCE
### 4.1 Response Format
**Status:****85% Compliant**
**Implemented:**
-`success_response()` used in custom actions
-`error_response()` used in custom actions
-`paginated_response()` via `CustomPageNumberPagination`
- ✅ Base ViewSet CRUD methods (retrieve, create, update, destroy) return unified format
- ✅ Exception handler wraps all errors in unified format
**Gaps:**
- ❌ Base ViewSet `list()` method not overridden - some ViewSets override it manually (Keywords, Clusters), others don't (Ideas, Tasks, Content, Images)
- ⚠️ Inconsistent: Some ViewSets use `get_paginated_response()` directly, others use `success_response()` for non-paginated
**Recommendation:** Override `list()` in base `SiteSectorModelViewSet` to ensure consistency.
---
### 4.2 Authentication & Permissions
**Status:****0% Compliant - CRITICAL GAP**
**Current State:**
-`KeywordViewSet`: `permission_classes = []` - **ALLOWS ANY ACCESS**
-`ClusterViewSet`: No `permission_classes` defined (inherits empty from base)
-`ContentIdeasViewSet`: No `permission_classes` defined
-`TasksViewSet`: No `permission_classes` defined
-`ContentViewSet`: No `permission_classes` defined
-`ImagesViewSet`: No `permission_classes` defined
**Required:**
- ✅ Should use `IsAuthenticatedAndActive` (from `igny8_core.api.permissions`)
- ✅ Should use `HasTenantAccess` (from `igny8_core.api.permissions`)
- ✅ Should use role-based permissions (`IsViewerOrAbove`, `IsEditorOrAbove`, etc.) for write operations
**Impact:** **CRITICAL SECURITY RISK** - All endpoints are publicly accessible without authentication.
---
### 4.3 Rate Limiting
**Status:****100% Compliant**
**Implemented:**
-`DebugScopedRateThrottle` used on all ViewSets
-`throttle_scope` set appropriately:
- Planner: `'planner'` (10/min for AI functions)
- Writer: `'writer'` (15/min for AI functions)
- ✅ Throttle rates configured in settings
- ✅ Debug bypass for development
---
### 4.4 Request ID Tracking
**Status:****100% Compliant**
**Implemented:**
-`RequestIDMiddleware` active
- ✅ Request ID included in responses via `get_request_id(request)`
- ✅ Response headers include `X-Request-ID`
---
### 4.5 Pagination
**Status:****100% Compliant**
**Implemented:**
-`CustomPageNumberPagination` used on all ViewSets
- ✅ Dynamic `page_size` support
- ✅ Unified response format with `success`, `count`, `next`, `previous`, `results`
- ✅ Request ID included in paginated responses
---
### 4.6 Error Handling
**Status:****95% Compliant**
**Implemented:**
-`custom_exception_handler` active
- ✅ All exceptions wrapped in unified format
- ✅ Debug information in DEBUG mode
- ✅ Proper logging
**Gaps:**
- ⚠️ Some custom actions have try-catch blocks that might bypass exception handler (but they use `error_response()` so it's fine)
---
## 5. FILTERS & SEARCH AUDIT
### 5.1 Planner Module Filters
#### Keywords Page
**Documented:** ✅ All implemented
- ✅ Search by keyword text
- ✅ Filter by status
- ✅ Filter by intent
- ✅ Filter by cluster
- ✅ Filter by difficulty range
- ✅ Filter by volume range
#### Clusters Page
**Documented:** ✅ All implemented
- ✅ Search by cluster name
- ✅ Filter by status
- ✅ Filter by volume range (backend + frontend)
- ✅ Filter by difficulty range (backend + frontend)
**Gap:** Documentation doesn't mention volume/difficulty range filters, but they're implemented and useful.
#### Ideas Page
**Documented:** ✅ All implemented
- ✅ Search by idea title
- ✅ Filter by status
- ✅ Filter by cluster
- ✅ Filter by content structure
- ✅ Filter by content type
---
### 5.2 Writer Module Filters
#### Tasks Page
**Documented:** ✅ All implemented
- ✅ Search by title or keywords
- ✅ Filter by status
- ✅ Filter by cluster
- ✅ Filter by content structure
- ✅ Filter by content type
#### Content Page
**Documented:** ✅ All implemented
- ✅ Search by title, meta_title, or primary_keyword
- ✅ Filter by status
- ✅ Filter by task_id
**Gap:** Documentation doesn't mention task_id filter, but it's implemented.
#### Images Page
**Documented:** ✅ All implemented
- ✅ Search (client-side filtering)
- ✅ Filter by status
**Note:** Images page uses grouped endpoint, so filtering is different from other pages.
---
## 6. BULK OPERATIONS AUDIT
### 6.1 Planner Module
#### Keywords
- ✅ Bulk delete
- ✅ Bulk update status
- ✅ Bulk add from seed
#### Clusters
- ✅ Bulk delete
- ❌ Bulk update status (not implemented, would be useful)
#### Ideas
- ✅ Bulk delete
- ✅ Bulk queue to writer
- ❌ Bulk update status (not implemented, would be useful)
---
### 6.2 Writer Module
#### Tasks
- ✅ Bulk delete
- ✅ Bulk update status
#### Content
- ❌ Bulk delete (not implemented, would be useful)
- ❌ Bulk update status (not implemented, would be useful)
#### Images
- ✅ Bulk update status (supports content_id or image IDs)
- ❌ Bulk delete (not implemented, would be useful)
---
## 7. IMPORT/EXPORT AUDIT
### 7.1 Planner Module
#### Keywords
- ✅ Export CSV (with filters and selected IDs support)
- ✅ Import CSV (with validation and duplicate checking)
#### Clusters
- ❌ Export CSV (not implemented)
- ❌ Import CSV (not implemented, not documented)
#### Ideas
- ❌ Export CSV (not implemented, not documented)
- ❌ Import CSV (not implemented, not documented)
---
### 7.2 Writer Module
#### Tasks
- ❌ Export CSV (not implemented, not documented)
- ❌ Import CSV (not implemented, not documented)
#### Content
- ❌ Export CSV (not implemented, not documented)
- ❌ Import CSV (not implemented, not documented)
#### Images
- ❌ Export CSV (not implemented, not documented)
- ❌ Import CSV (not implemented, not documented)
**Note:** Import/Export for these entities may not be necessary, but Keywords export is very useful, so similar functionality for other entities could be valuable.
---
## 8. CRITICAL GAPS SUMMARY
### 🔴 **CRITICAL (Security & Compliance)**
1. **Missing Permission Classes** - **ALL ViewSets**
- **Impact:** All endpoints publicly accessible
- **Fix:** Add `permission_classes = [IsAuthenticatedAndActive, HasTenantAccess]` to all ViewSets
- **Priority:** **P0 - IMMEDIATE**
2. **Inconsistent `list()` Method**
- **Impact:** Some ViewSets return unified format, others might not
- **Fix:** Override `list()` in base `SiteSectorModelViewSet`
- **Priority:** **P1 - HIGH**
---
### 🟡 **HIGH PRIORITY (Functionality)**
3. **Missing Export Functionality**
- Clusters, Ideas, Tasks, Content, Images
- **Priority:** **P2 - MEDIUM** (Keywords export is most important, others are nice-to-have)
4. **Missing Bulk Operations**
- Content: bulk delete, bulk update status
- Images: bulk delete
- Clusters: bulk update status
- Ideas: bulk update status
- **Priority:** **P2 - MEDIUM**
---
### 🟢 **LOW PRIORITY (Enhancements)**
5. **Missing Import Functionality**
- Clusters, Ideas, Tasks, Content, Images
- **Priority:** **P3 - LOW** (Import is less critical than export)
6. **Filter UI Improvements**
- Difficulty range slider instead of dropdown
- Volume range UI consistency
- **Priority:** **P3 - LOW**
---
## 9. RECOMMENDATIONS
### Immediate Actions (This Week)
1. **Add Permission Classes to All ViewSets**
```python
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess]
```
2. **Override `list()` in Base ViewSet**
```python
# In SiteSectorModelViewSet
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return success_response(data=serializer.data, request=request)
```
### Short-term Actions (This Month)
3. **Add Export Functionality**
- Start with Clusters and Ideas (most requested)
- Follow Keywords export pattern
- Support filters and selected IDs
4. **Add Missing Bulk Operations**
- Content bulk delete and update status
- Images bulk delete
- Clusters and Ideas bulk update status
### Long-term Enhancements (Next Quarter)
5. **Import Functionality**
- Evaluate need for each entity
- Implement for high-value entities (Tasks, Content)
6. **Filter UI Improvements**
- Standardize range filter UI
- Add more filter options where useful
---
## 10. METRICS & STATISTICS
### Implementation Coverage
| Module | Pages | CRUD | Filters | Bulk Ops | Import | Export | AI Functions | Permissions |
|--------|-------|------|---------|----------|--------|--------|--------------|-------------|
| **Planner** | | | | | | | | |
| Keywords | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Clusters | ✅ | ✅ | ✅ | ⚠️ | ❌ | ❌ | ✅ | ❌ |
| Ideas | ✅ | ✅ | ✅ | ⚠️ | ❌ | ❌ | ✅ | ❌ |
| **Writer** | | | | | | | | |
| Tasks | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Content | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| Images | ✅ | ✅ | ✅ | ⚠️ | ❌ | ❌ | ✅ | ❌ |
**Legend:**
- ✅ Fully implemented
- ⚠️ Partially implemented
- ❌ Not implemented
### Overall Scores
- **CRUD Operations:** 100% ✅
- **Filters & Search:** 95% ✅
- **Bulk Operations:** 75% ⚠️
- **Import/Export:** 17% ❌ (only Keywords)
- **AI Functions:** 100% ✅
- **API Standard Compliance:** 80% ⚠️ (missing permissions)
- **Security:** 0% ❌ (missing permissions)
---
## 11. CONCLUSION
The Planner and Writer modules are **85% complete** with strong implementation of core functionality, AI functions, and most CRUD operations. The primary gaps are:
1. **Security:** Missing permission classes on all ViewSets - **CRITICAL**
2. **Consistency:** Base ViewSet `list()` method not overridden - **HIGH PRIORITY**
3. **Functionality:** Missing export for most entities and some bulk operations - **MEDIUM PRIORITY**
**Recommendation:** Address security gaps immediately, then focus on export functionality and missing bulk operations. The modules are production-ready after permission classes are added.
---
**Report Generated:** 2025-01-XX
**Next Review:** After permission classes implementation

View File

@@ -44,7 +44,8 @@ igny8/
│ ├── 03-FRONTEND-ARCHITECTURE.md │ ├── 03-FRONTEND-ARCHITECTURE.md
│ ├── 04-BACKEND-IMPLEMENTATION.md │ ├── 04-BACKEND-IMPLEMENTATION.md
│ ├── 05-AI-FRAMEWORK-IMPLEMENTATION.md │ ├── 05-AI-FRAMEWORK-IMPLEMENTATION.md
── 06-FUNCTIONAL-BUSINESS-LOGIC.md ── 06-FUNCTIONAL-BUSINESS-LOGIC.md
│ └── API-COMPLETE-REFERENCE.md # Complete unified API documentation
├── CHANGELOG.md # Version history and changes (only updated after user confirmation) ├── CHANGELOG.md # Version history and changes (only updated after user confirmation)
└── docker-compose.app.yml └── docker-compose.app.yml
``` ```
@@ -132,16 +133,62 @@ For complete installation guide, see [docs/01-TECH-STACK-AND-INFRASTRUCTURE.md](
--- ---
## 🔗 API Endpoints ## 🔗 API Documentation
- **Planner**: `/api/v1/planner/keywords/`, `/api/v1/planner/clusters/`, `/api/v1/planner/ideas/` ### Interactive Documentation
- **Writer**: `/api/v1/writer/tasks/`, `/api/v1/writer/images/`
- **System**: `/api/v1/system/settings/`
- **Billing**: `/api/v1/billing/`
- **Auth**: `/api/v1/auth/`
- **Admin**: `/admin/`
See [docs/04-BACKEND-IMPLEMENTATION.md](docs/04-BACKEND-IMPLEMENTATION.md) for complete API reference. - **Swagger UI**: `https://api.igny8.com/api/docs/`
- **ReDoc**: `https://api.igny8.com/api/redoc/`
- **OpenAPI Schema**: `https://api.igny8.com/api/schema/`
### API Complete Reference
**[API Complete Reference](docs/API-COMPLETE-REFERENCE.md)** - Comprehensive unified API documentation (single source of truth)
- Complete endpoint reference (100+ endpoints across all modules)
- Authentication & authorization guide
- Response format standards (unified format: `{success, data, message, errors, request_id}`)
- Error handling
- Rate limiting (scoped by operation type)
- Pagination
- Roles & permissions
- Tenant/site/sector scoping
- Integration examples (Python, JavaScript, cURL, PHP)
- Testing & debugging
- Change management
### API Standard Features
- ✅ **Unified Response Format** - Consistent JSON structure for all endpoints
- ✅ **Layered Authorization** - Authentication → Tenant → Role → Site/Sector
- ✅ **Centralized Error Handling** - All errors in unified format with request_id
- ✅ **Scoped Rate Limiting** - Different limits per operation type (10-100/min)
- ✅ **Tenant Isolation** - Account/site/sector scoping
- ✅ **Request Tracking** - Unique request ID for debugging
- ✅ **100% Implemented** - All endpoints use unified format
### Quick API Example
```bash
# Login
curl -X POST https://api.igny8.com/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}'
# Get keywords (with token)
curl -X GET https://api.igny8.com/api/v1/planner/keywords/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json"
```
### Additional API Guides
- **[Authentication Guide](docs/AUTHENTICATION-GUIDE.md)** - Detailed JWT authentication guide
- **[Error Codes Reference](docs/ERROR-CODES.md)** - Complete error code reference
- **[Rate Limiting Guide](docs/RATE-LIMITING.md)** - Rate limiting and throttling details
- **[Migration Guide](docs/MIGRATION-GUIDE.md)** - Migrating to API v1.0
- **[WordPress Plugin Integration](docs/WORDPRESS-PLUGIN-INTEGRATION.md)** - WordPress integration guide
For backend implementation details, see [docs/04-BACKEND-IMPLEMENTATION.md](docs/04-BACKEND-IMPLEMENTATION.md).
--- ---
@@ -234,6 +281,7 @@ All documentation is consolidated in the `/docs/` folder.
### Finding Information ### Finding Information
**By Topic:** **By Topic:**
- **API Documentation**: [API-COMPLETE-REFERENCE.md](docs/API-COMPLETE-REFERENCE.md) - Complete unified API reference (single source of truth)
- **Infrastructure & Deployment**: [01-TECH-STACK-AND-INFRASTRUCTURE.md](docs/01-TECH-STACK-AND-INFRASTRUCTURE.md) - **Infrastructure & Deployment**: [01-TECH-STACK-AND-INFRASTRUCTURE.md](docs/01-TECH-STACK-AND-INFRASTRUCTURE.md)
- **Application Architecture**: [02-APPLICATION-ARCHITECTURE.md](docs/02-APPLICATION-ARCHITECTURE.md) - **Application Architecture**: [02-APPLICATION-ARCHITECTURE.md](docs/02-APPLICATION-ARCHITECTURE.md)
- **Frontend Development**: [03-FRONTEND-ARCHITECTURE.md](docs/03-FRONTEND-ARCHITECTURE.md) - **Frontend Development**: [03-FRONTEND-ARCHITECTURE.md](docs/03-FRONTEND-ARCHITECTURE.md)

View File

@@ -1,262 +0,0 @@
# Orphan Code and Unused Files Audit
## AI Framework - Complete Analysis
**Date:** 2025-01-XX
**Status:** Audit Complete
**Purpose:** Identify orphan AI functions, unused files, and code not part of active processes
---
## Summary
### Active AI Functions: 5 ✅
All registered functions are actively used from views.
### Orphan Functions: 0 ✅
No registered functions are unused.
### Unused Files: 0 ✅
All files in `/ai/` folder are part of active chain.
### Orphan Code/Exports: 4 ❌
Functions exported in `__init__.py` but never imported/used.
### Parallel/Old Code: 1 ⚠️
Old `utils/ai_processor.py` still used for testing (parallel system).
---
## Active AI Functions (5 Total)
| Function Name | View/Endpoint | Function File | Status |
|--------------|---------------|---------------|--------|
| `auto_cluster` | `planner/views.py``KeywordViewSet.auto_cluster()` | `functions/auto_cluster.py` | ✅ Active |
| `auto_generate_ideas``generate_ideas` | `planner/views.py``ClusterViewSet.auto_generate_ideas()` | `functions/generate_ideas.py` | ✅ Active |
| `generate_content` | `writer/views.py``TaskViewSet.auto_generate_content()` | `functions/generate_content.py` | ✅ Active |
| `generate_image_prompts` | `writer/views.py``ContentViewSet.generate_image_prompts()` | `functions/generate_image_prompts.py` | ✅ Active |
| `generate_images` | `writer/views.py``ImageViewSet.generate_images()` | `functions/generate_images.py` | ✅ Active |
**Result:** All 5 registered functions are actively used. No orphan functions.
---
## Files in `/ai/` Folder - Usage Analysis
### Core Framework Files (All Active ✅)
| File | Purpose | Used By | Status |
|------|---------|---------|--------|
| `tasks.py` | Celery task entry point | `planner/views.py`, `writer/views.py` | ✅ Active |
| `engine.py` | AI function orchestrator | `tasks.py` | ✅ Active |
| `ai_core.py` | AI request handler | `engine.py`, `functions/*.py` | ✅ Active |
| `base.py` | Base function class | All `functions/*.py` | ✅ Active |
| `registry.py` | Function registry | `tasks.py`, `engine.py` | ✅ Active |
| `prompts.py` | Prompt management | All `functions/*.py` | ✅ Active |
| `tracker.py` | Progress tracking | `engine.py` | ✅ Active |
| `validators.py` | Validation helpers | All `functions/*.py` | ✅ Active |
| `settings.py` | Model configuration | `engine.py`, `ai_core.py` | ✅ Active |
| `constants.py` | Constants (rates, models) | `ai_core.py`, `validators.py`, `utils/ai_processor.py` | ✅ Active |
| `models.py` | AITaskLog model | `engine.py` | ✅ Active |
| `admin.py` | Django admin | Django admin interface | ✅ Active |
| `apps.py` | Django app config | Django framework | ✅ Active |
| `__init__.py` | Package exports | Various imports | ✅ Active |
**Result:** All files in `/ai/` folder are part of active chain. No unused files.
---
## Function Files (All Active ✅)
| File | Function Class | Registered | Used From View | Status |
|------|---------------|------------|----------------|--------|
| `functions/auto_cluster.py` | `AutoClusterFunction` | ✅ | `planner/views.py` | ✅ Active |
| `functions/generate_ideas.py` | `GenerateIdeasFunction` | ✅ | `planner/views.py` | ✅ Active |
| `functions/generate_content.py` | `GenerateContentFunction` | ✅ | `writer/views.py` | ✅ Active |
| `functions/generate_image_prompts.py` | `GenerateImagePromptsFunction` | ✅ | `writer/views.py` | ✅ Active |
| `functions/generate_images.py` | `GenerateImagesFunction` | ✅ | `writer/views.py` | ✅ Active |
**Result:** All 5 function files are registered and actively used. No orphan functions.
---
## Orphan Code - Exported But Never Used
### 1. `get_model()` Function ❌
**Location:** `settings.py` line 106-109
**Exported in:** `__init__.py` line 69
**Used:** ❌ Never imported or called anywhere
```python
def get_model(function_name: str) -> str:
"""Get model name for function"""
config = get_model_config(function_name)
return config.get("model", "gpt-4.1")
```
**Recommendation:** Remove from `__init__.py` exports and delete function (or keep if planned for future use).
---
### 2. `get_max_tokens()` Function ❌
**Location:** `settings.py` line 112-115
**Exported in:** `__init__.py` line 70
**Used:** ❌ Never imported or called anywhere
```python
def get_max_tokens(function_name: str) -> int:
"""Get max tokens for function"""
config = get_model_config(function_name)
return config.get("max_tokens", 4000)
```
**Recommendation:** Remove from `__init__.py` exports and delete function (or keep if planned for future use).
---
### 3. `get_temperature()` Function ❌
**Location:** `settings.py` line 118-121
**Exported in:** `__init__.py` line 71
**Used:** ❌ Never imported or called anywhere
```python
def get_temperature(function_name: str) -> float:
"""Get temperature for function"""
config = get_model_config(function_name)
return config.get("temperature", 0.7)
```
**Recommendation:** Remove from `__init__.py` exports and delete function (or keep if planned for future use).
---
### 4. `register_function()` Function ❌
**Location:** `registry.py` line 15-21
**Exported in:** `__init__.py` line 44
**Used:** ❌ Never called anywhere (only `register_lazy_function` is used)
```python
def register_function(name: str, function_class: Type[BaseAIFunction]):
"""Register an AI function"""
if not issubclass(function_class, BaseAIFunction):
raise ValueError(f"{function_class} must inherit from BaseAIFunction")
_FUNCTION_REGISTRY[name] = function_class
logger.info(f"Registered AI function: {name}")
```
**Recommendation:** Keep for potential future use (direct registration), but remove from `__init__.py` exports if not needed.
---
### 5. `list_functions()` Function ❌
**Location:** `registry.py` line 50-52
**Exported in:** `__init__.py` line 46
**Used:** ❌ Never called anywhere
```python
def list_functions() -> list:
"""List all registered functions"""
return list(_FUNCTION_REGISTRY.keys())
```
**Recommendation:** Keep for debugging/admin purposes, but remove from `__init__.py` exports if not needed.
---
## Internal Helper Functions (Not Orphan ✅)
### `extract_image_prompts` Config Entry ✅
**Location:** `settings.py` line 31-36 (MODEL_CONFIG)
**Used by:** `functions/generate_images.py` line 116, 126, 135
**Status:** ✅ Active (internal helper, not a registered function)
This is **NOT** an orphan. It's an internal config entry used by `GenerateImagesFunction` to get model config for extracting image prompts. It's not a registered AI function, just a config key.
**Recommendation:** Keep - it's part of active chain.
---
## Parallel/Old Code System
### `utils/ai_processor.py` ⚠️
**Location:** `/backend/igny8_core/utils/ai_processor.py`
**Status:** ⚠️ Still used, but parallel to new AI framework
**Used by:** `modules/system/integration_views.py` (image generation testing)
**Details:**
- Old AI processing system (pre-framework)
- Still used in `integration_views.py` for:
- Image generation testing (`generate_image()` method)
- Model rates display (`MODEL_RATES` import)
- Parallel to new AI framework (not part of it)
- Has deprecated methods: `cluster_keywords()` (line 1070 warns it's deprecated)
**Recommendation:**
- **Option 1:** Keep for now (used in integration testing)
- **Option 2:** Refactor `integration_views.py` to use new AI framework instead
- **Option 3:** Mark as deprecated and plan migration
**Note:** This is outside `/ai/` folder, so not part of this audit scope, but worth noting.
---
## Summary Table
| Category | Count | Status |
|----------|-------|--------|
| **Active AI Functions** | 5 | ✅ All used |
| **Orphan AI Functions** | 0 | ✅ None found |
| **Unused Files in `/ai/`** | 0 | ✅ All active |
| **Orphan Exports** | 4 | ❌ `get_model()`, `get_max_tokens()`, `get_temperature()`, `register_function()`, `list_functions()` |
| **Internal Helpers** | 1 | ✅ `extract_image_prompts` (active) |
| **Parallel Systems** | 1 | ⚠️ `utils/ai_processor.py` (old code) |
---
## Recommendations
### High Priority
1. **Remove Orphan Exports from `__init__.py`**
- Remove `get_model`, `get_max_tokens`, `get_temperature` from exports
- These functions are never used and add confusion
2. **Clean Up `settings.py`**
- After removing MODEL_CONFIG (per refactoring plan), these helper functions become even less useful
- Consider removing them entirely or keeping only if needed for future use
### Medium Priority
3. **Review `register_function()` and `list_functions()`**
- Decide if these are needed for future direct registration
- If not needed, remove from exports
- If needed, document their purpose
### Low Priority
4. **Consider Migrating `utils/ai_processor.py`**
- Refactor `integration_views.py` to use new AI framework
- Remove old `ai_processor.py` system
- This is a larger refactoring task
---
## Action Items
- [ ] Remove `get_model`, `get_max_tokens`, `get_temperature` from `__init__.py` exports
- [ ] Delete or comment out unused helper functions in `settings.py`
- [ ] Review and decide on `register_function()` and `list_functions()` exports
- [ ] Document decision on `utils/ai_processor.py` migration (future work)
---
**Last Updated:** 2025-01-XX
**Status:** Audit Complete - Ready for Cleanup

View File

@@ -0,0 +1,342 @@
# AI Framework Refactoring - Implementation Complete
## Remove Hardcoded Model Defaults - IntegrationSettings Only
**Date Implemented:** 2025-01-XX
**Status:****COMPLETED**
**Why:** To enforce account-specific model configuration and eliminate hardcoded fallbacks that could lead to unexpected behavior or security issues.
---
## Executive Summary
This refactoring successfully removed all hardcoded model defaults and fallbacks from the AI framework, making `IntegrationSettings` the single source of truth for model configuration. This ensures:
1. **Account Isolation**: Each account must configure their own AI models
2. **No Silent Fallbacks**: Missing configuration results in clear, actionable errors
3. **Security**: Prevents accidental use of default models that may not be appropriate for an account
4. **Code Clarity**: Removed orphan code and simplified the configuration system
---
## What Was Changed
### Problem Statement
**Before Refactoring:**
The AI framework had a 3-tier fallback system:
1. **Priority 1:** IntegrationSettings (account-specific) ✅
2. **Priority 2:** MODEL_CONFIG hardcoded defaults ❌
3. **Priority 3:** Django settings DEFAULT_AI_MODEL ❌
This created several issues:
- Silent fallbacks could mask configuration problems
- Hardcoded defaults could be used unintentionally
- No clear indication when IntegrationSettings were missing
- Orphan code cluttered the codebase
**After Refactoring:**
- **Single Source:** IntegrationSettings only (account-specific)
- **No Fallbacks:** Missing IntegrationSettings → clear error message
- **Account-Specific:** Each account must configure their own models
- **Clean Codebase:** Orphan code removed
---
## Implementation Details
### 1. `settings.py` - Model Configuration
**Changes Made:**
- ✅ Removed `MODEL_CONFIG` dictionary (lines 7-43) - eliminated hardcoded defaults
- ✅ Updated `get_model_config()` to require `account` parameter (no longer optional)
- ✅ Removed fallback to `default_config` - now raises `ValueError` if IntegrationSettings not found
- ✅ Removed unused helper functions: `get_model()`, `get_max_tokens()`, `get_temperature()`
**New Behavior:**
```python
def get_model_config(function_name: str, account) -> Dict[str, Any]:
"""
Get model configuration from IntegrationSettings only.
No fallbacks - account must have IntegrationSettings configured.
Raises:
ValueError: If account not provided or IntegrationSettings not configured
"""
if not account:
raise ValueError("Account is required for model configuration")
# Get IntegrationSettings for OpenAI
integration_settings = IntegrationSettings.objects.get(
integration_type='openai',
account=account,
is_active=True
)
# Validate model is configured
model = config.get('model')
if not model:
raise ValueError(
f"Model not configured in IntegrationSettings for account {account.id}. "
f"Please set 'model' in OpenAI integration settings."
)
return {
'model': model,
'max_tokens': config.get('max_tokens', 4000),
'temperature': config.get('temperature', 0.7),
'response_format': response_format, # JSON mode for supported models
}
```
**Error Messages:**
- Missing account: `"Account is required for model configuration"`
- Missing IntegrationSettings: `"OpenAI IntegrationSettings not configured for account {id}. Please configure OpenAI settings in the integration page."`
- Missing model: `"Model not configured in IntegrationSettings for account {id}. Please set 'model' in OpenAI integration settings."`
---
### 2. `ai_core.py` - Default Model Fallback
**Changes Made:**
- ✅ Removed `_default_model` initialization (was reading from Django settings)
- ✅ Updated `run_ai_request()` to require `model` parameter (no fallback)
- ✅ Added validation to raise `ValueError` if model not provided
- ✅ Deprecated `get_model()` method (now raises `ValueError`)
**New Behavior:**
```python
def run_ai_request(self, prompt: str, model: str, ...):
"""
Model parameter is now required - no fallback to default.
"""
if not model:
raise ValueError("Model is required. Ensure IntegrationSettings is configured for the account.")
active_model = model # No fallback
# ... rest of implementation
```
---
### 3. `engine.py` - Model Configuration Call
**Changes Made:**
- ✅ Added validation to ensure `self.account` exists before calling `get_model_config()`
- ✅ Wrapped `get_model_config()` call in try-except to handle `ValueError` gracefully
- ✅ Improved error handling to preserve exception types for better error messages
**New Behavior:**
```python
# Validate account exists
if not self.account:
raise ValueError("Account is required for AI function execution")
# Get model config with proper error handling
try:
model_config = get_model_config(function_name, account=self.account)
model = model_config.get('model')
except ValueError as e:
# IntegrationSettings not configured or model missing
error_msg = str(e)
error_type = 'ConfigurationError'
return self._handle_error(error_msg, fn, error_type=error_type)
except Exception as e:
# Other unexpected errors
error_msg = f"Failed to get model configuration: {str(e)}"
error_type = type(e).__name__
return self._handle_error(error_msg, fn, error_type=error_type)
```
**Error Handling Improvements:**
- Preserves exception types (`ConfigurationError`, `ValueError`, etc.)
- Provides clear error messages to frontend
- Logs errors with proper context
---
### 4. `tasks.py` - Task Entry Point
**Changes Made:**
- ✅ Made `account_id` a required parameter (no longer optional)
- ✅ Added validation to ensure `account_id` is provided
- ✅ Added validation to ensure `Account` exists in database
- ✅ Improved error responses to include `error_type`
**New Behavior:**
```python
@shared_task(bind=True, max_retries=3)
def run_ai_task(self, function_name: str, payload: dict, account_id: int):
"""
account_id is now required - no optional parameter.
"""
# Validate account_id is provided
if not account_id:
error_msg = "account_id is required for AI task execution"
return {
'success': False,
'error': error_msg,
'error_type': 'ConfigurationError'
}
# Validate account exists
try:
account = Account.objects.get(id=account_id)
except Account.DoesNotExist:
error_msg = f"Account {account_id} not found"
return {
'success': False,
'error': error_msg,
'error_type': 'AccountNotFound'
}
# ... rest of implementation
```
---
### 5. Orphan Code Cleanup
**Changes Made:**
#### `__init__.py` - Removed Orphan Exports
- ✅ Removed `get_model`, `get_max_tokens`, `get_temperature` from `__all__` export list
- ✅ Removed `register_function`, `list_functions` from `__all__` export list
- ✅ Removed unused imports from `settings.py` (`MODEL_CONFIG`, `get_model`, `get_max_tokens`, `get_temperature`)
#### `settings.py` - Removed Unused Helper Functions
- ✅ Removed `get_model()` function (lines 106-109)
- ✅ Removed `get_max_tokens()` function (lines 112-115)
- ✅ Removed `get_temperature()` function (lines 118-121)
**Rationale:**
- These functions were never imported or used anywhere in the codebase
- `get_model_config()` already returns all needed values
- Removing them simplifies the API and reduces maintenance burden
---
## Testing & Verification
### Unit Tests Created
**File:** `backend/igny8_core/api/tests/test_ai_framework.py`
**Test Coverage:**
1.`get_model_config()` with valid IntegrationSettings
2.`get_model_config()` without account (raises ValueError)
3.`get_model_config()` without IntegrationSettings (raises ValueError)
4.`get_model_config()` without model in config (raises ValueError)
5.`get_model_config()` with inactive IntegrationSettings (raises ValueError)
6.`get_model_config()` with function aliases (backward compatibility)
7.`get_model_config()` with JSON mode models
8.`AICore.run_ai_request()` without model (raises ValueError)
9.`AICore.run_ai_request()` with empty model string (raises ValueError)
10. ✅ Deprecated `get_model()` method (raises ValueError)
**All Tests:****PASSING**
### Manual Testing
**Tested All 5 AI Functions:**
1.`auto_cluster` - Works with valid IntegrationSettings
2.`generate_ideas` - Works with valid IntegrationSettings
3.`generate_content` - Works with valid IntegrationSettings
4.`generate_image_prompts` - Works with valid IntegrationSettings
5.`generate_images` - Works with valid IntegrationSettings
**Error Cases Tested:**
- ✅ All functions show clear error messages when IntegrationSettings not configured
- ✅ Error messages are user-friendly and actionable
- ✅ Errors include proper `error_type` for frontend handling
---
## Impact Analysis
### Breaking Changes
**None** - This is a refactoring, not a breaking change:
- Existing accounts with IntegrationSettings configured continue to work
- No API changes
- No database migrations required
- Frontend error handling already supports the new error format
### Benefits
1. **Security**: Prevents accidental use of default models
2. **Clarity**: Clear error messages guide users to configure IntegrationSettings
3. **Maintainability**: Removed orphan code reduces maintenance burden
4. **Consistency**: Single source of truth for model configuration
5. **Account Isolation**: Each account must explicitly configure their models
### Migration Path
**For Existing Accounts:**
- Accounts with IntegrationSettings configured: ✅ No action needed
- Accounts without IntegrationSettings: Must configure OpenAI settings in integration page
**For Developers:**
- All AI functions now require `account_id` parameter
- `get_model_config()` now requires `account` parameter (no longer optional)
- Error handling must account for `ConfigurationError` and `AccountNotFound` error types
---
## Files Modified
### Core Framework Files
1. `backend/igny8_core/ai/settings.py` - Removed MODEL_CONFIG, updated get_model_config()
2. `backend/igny8_core/ai/ai_core.py` - Removed _default_model, updated run_ai_request()
3. `backend/igny8_core/ai/engine.py` - Added account validation, improved error handling
4. `backend/igny8_core/ai/tasks.py` - Made account_id required, added validation
5. `backend/igny8_core/ai/__init__.py` - Removed orphan exports and imports
### Test Files
6. `backend/igny8_core/api/tests/test_ai_framework.py` - Created comprehensive unit tests
### Function Files (No Changes Required)
- All 5 AI function files work without modification
- They inherit the new behavior from base classes
---
## Success Criteria - All Met ✅
- [x] All 5 active AI functions work with IntegrationSettings only
- [x] Clear error messages when IntegrationSettings not configured
- [x] No hardcoded model defaults remain
- [x] No Django settings fallbacks remain
- [x] Orphan code removed (orphan exports, unused functions)
- [x] No broken imports after cleanup
- [x] All tests pass
- [x] Documentation updated
- [x] Frontend handles errors gracefully
---
## Related Documentation
- **AI Framework Implementation:** `docs/05-AI-FRAMEWORK-IMPLEMENTATION.md` (updated)
- **Changelog:** `CHANGELOG.md` (updated with refactoring details)
- **Orphan Code Audit:** `backend/igny8_core/ai/ORPHAN-CODE-AUDIT.md` (temporary file, can be removed)
---
## Future Considerations
### Potential Enhancements
1. **Model Validation**: Could add validation against supported models list
2. **Default Suggestions**: Could provide default model suggestions in UI
3. **Migration Tool**: Could create a tool to help migrate accounts without IntegrationSettings
### Maintenance Notes
- All model configuration must go through IntegrationSettings
- No hardcoded defaults should be added in the future
- Error messages should remain user-friendly and actionable
---
**Last Updated:** 2025-01-XX
**Status:****IMPLEMENTATION COMPLETE**
**Version:** 1.1.2

View File

@@ -1,520 +0,0 @@
# AI Framework Refactoring Plan
## Remove Hardcoded Model Defaults - IntegrationSettings Only
**Date:** 2025-01-XX
**Status:** Planning
**Goal:**
1. Remove Priority 2 (MODEL_CONFIG) and Priority 3 (Django settings) fallbacks. Use only IntegrationSettings (account-specific configuration).
2. Clean up orphan code: Remove unused exports and helper functions.
---
## Overview
### Current Problem
The AI framework has a 3-tier fallback system:
1. **Priority 1:** IntegrationSettings (account-specific) ✅ Keep
2. **Priority 2:** MODEL_CONFIG hardcoded defaults ❌ Remove
3. **Priority 3:** Django settings DEFAULT_AI_MODEL ❌ Remove
### Target State
- **Single Source:** IntegrationSettings only (account-specific)
- **No Fallbacks:** If IntegrationSettings not configured → raise clear error
- **Account-Specific:** Each account must configure their own models
---
## Active AI Functions (5 Total)
These are the only functions that need to work after refactoring:
| Function Name | View/Endpoint | Function File | Status |
|--------------|---------------|---------------|--------|
| `auto_cluster` | `planner/views.py``KeywordViewSet.auto_cluster()` | `functions/auto_cluster.py` | ✅ Active |
| `auto_generate_ideas``generate_ideas` | `planner/views.py``ClusterViewSet.auto_generate_ideas()` | `functions/generate_ideas.py` | ✅ Active |
| `generate_content` | `writer/views.py``TaskViewSet.auto_generate_content()` | `functions/generate_content.py` | ✅ Active |
| `generate_image_prompts` | `writer/views.py``ContentViewSet.generate_image_prompts()` | `functions/generate_image_prompts.py` | ✅ Active |
| `generate_images` | `writer/views.py``ImageViewSet.generate_images()` | `functions/generate_images.py` | ✅ Active |
**Note:** `process_image_generation_queue` is a Celery task (not an AI function) - excluded from this refactoring.
---
## Files to Modify
### 1. `settings.py` - Model Configuration
**Current Issues:**
- Lines 7-43: `MODEL_CONFIG` dict with hardcoded defaults
- Line 71: Gets config from `MODEL_CONFIG` first
- Lines 96-103: Falls back to `default_config` if IntegrationSettings not found
**Changes Required:**
#### Remove MODEL_CONFIG Dict (Lines 7-43)
```python
# REMOVE THIS ENTIRE SECTION:
MODEL_CONFIG = {
"auto_cluster": {
"model": "gpt-4o-mini",
"max_tokens": 4000,
"temperature": 0.7,
},
"generate_ideas": {
"model": "gpt-4o-mini",
"max_tokens": 4000,
"temperature": 0.7,
},
# ... rest of dict
}
```
#### Update `get_model_config()` Function (Lines 55-103)
**Current Logic:**
```python
def get_model_config(function_name, account=None):
# 1. Get from MODEL_CONFIG (hardcoded) ❌
config = MODEL_CONFIG.get(actual_name, {}).copy()
# 2. Try IntegrationSettings (if account provided)
if account:
# ... get from IntegrationSettings
if model_from_settings:
config['model'] = model_from_settings # Override
# 3. Fallback to default_config ❌
if not config.get('model'):
default_config = {"model": "gpt-4.1", ...}
config.update(default_config)
return config
```
**New Logic:**
```python
def get_model_config(function_name, account=None):
"""
Get model configuration from IntegrationSettings only.
No fallbacks - account must have IntegrationSettings configured.
Args:
function_name: Name of the AI function
account: Account instance (required)
Returns:
dict: Model configuration with 'model', 'max_tokens', 'temperature'
Raises:
ValueError: If account not provided or IntegrationSettings not configured
"""
if not account:
raise ValueError("Account is required for model configuration")
# Resolve function alias
actual_name = FUNCTION_ALIASES.get(function_name, function_name)
# Get IntegrationSettings for OpenAI
try:
integration_settings = IntegrationSettings.objects.get(
integration_type='openai',
account=account,
is_active=True
)
except IntegrationSettings.DoesNotExist:
raise ValueError(
f"OpenAI IntegrationSettings not configured for account {account.id}. "
f"Please configure OpenAI settings in the integration page."
)
config = integration_settings.config or {}
# Get model from config
model = config.get('model')
if not model:
raise ValueError(
f"Model not configured in IntegrationSettings for account {account.id}. "
f"Please set 'model' in OpenAI integration settings."
)
# Get max_tokens and temperature from config (with reasonable defaults for API)
max_tokens = config.get('max_tokens', 4000) # Reasonable default for API limits
temperature = config.get('temperature', 0.7) # Reasonable default
return {
'model': model,
'max_tokens': max_tokens,
'temperature': temperature,
}
```
**Key Changes:**
- Remove `MODEL_CONFIG` lookup
- Require `account` parameter (no optional)
- Raise `ValueError` if IntegrationSettings not found
- Raise `ValueError` if model not configured
- No fallbacks to hardcoded defaults
---
### 2. `ai_core.py` - Default Model Fallback
**Current Issues:**
- Lines 84-90: `_default_model` initialized from Django settings
- Line 159: Falls back to `self._default_model` if model not provided
**Changes Required:**
#### Remove `_default_model` Initialization (Lines 84-90)
```python
# REMOVE THIS:
try:
from django.conf import settings
self._default_model = getattr(settings, 'DEFAULT_AI_MODEL', 'gpt-4.1')
except:
self._default_model = 'gpt-4.1'
```
#### Update `run_ai_request()` Method (Line 159)
**Current Logic:**
```python
active_model = model or self._default_model # ❌ Fallback
```
**New Logic:**
```python
if not model:
raise ValueError("Model is required. Ensure IntegrationSettings is configured for the account.")
active_model = model
```
**Key Changes:**
- Remove `_default_model` attribute
- Require `model` parameter (no fallback)
- Raise `ValueError` if model not provided
---
### 3. `engine.py` - Model Configuration Call
**Current Issues:**
- Line 206: Calls `get_model_config(function_name, account=self.account)`
- May pass `account=None` in some cases
**Changes Required:**
#### Ensure Account is Always Passed (Line 206)
```python
# Current:
model_config = get_model_config(function_name, account=self.account)
# New (same, but ensure account is not None):
if not self.account:
raise ValueError("Account is required for AI function execution")
model_config = get_model_config(function_name, account=self.account)
```
**Key Changes:**
- Validate `self.account` exists before calling `get_model_config()`
- Raise clear error if account missing
---
### 4. `tasks.py` - Task Entry Point
**Current Issues:**
- May not always have account context
**Changes Required:**
#### Ensure Account is Always Available (Line ~50)
```python
# In run_ai_task() function, ensure account is always set:
if not account_id:
raise ValueError("account_id is required for AI task execution")
account = Account.objects.get(id=account_id)
```
**Key Changes:**
- Validate `account_id` is provided
- Ensure account exists before proceeding
---
## Orphan Code Cleanup
Based on the audit in `ORPHAN-CODE-AUDIT.md`, the following orphan code should be removed:
### 5. `__init__.py` - Remove Orphan Exports
**Current Issues:**
- Lines 69-71: Export `get_model`, `get_max_tokens`, `get_temperature` - never imported/used
- Line 44: Export `register_function` - never called (only `register_lazy_function` is used)
- Line 46: Export `list_functions` - never called
**Changes Required:**
#### Remove Orphan Exports (Lines 44, 46, 69-71)
```python
# REMOVE FROM __all__:
'register_function', # Line 44 - never called
'list_functions', # Line 46 - never called
'get_model', # Line 69 - never imported
'get_max_tokens', # Line 70 - never imported
'get_temperature', # Line 71 - never imported
```
#### Remove Orphan Imports (Lines 29-35)
```python
# REMOVE THESE IMPORTS:
from igny8_core.ai.settings import (
MODEL_CONFIG, # Will be removed in Step 2
get_model_config, # Keep - actively used
get_model, # ❌ Remove - never used
get_max_tokens, # ❌ Remove - never used
get_temperature, # ❌ Remove - never used
)
```
**Key Changes:**
- Remove unused exports from `__all__`
- Remove unused imports
- Keep `get_model_config` (actively used)
- Keep `register_function` and `list_functions` in registry.py (may be useful for debugging), but don't export them
---
### 6. `settings.py` - Remove Unused Helper Functions
**Current Issues:**
- Lines 106-109: `get_model()` function - never called
- Lines 112-115: `get_max_tokens()` function - never called
- Lines 118-121: `get_temperature()` function - never called
**Changes Required:**
#### Remove Unused Helper Functions (Lines 106-121)
```python
# REMOVE THESE FUNCTIONS:
def get_model(function_name: str) -> str:
"""Get model name for function"""
config = get_model_config(function_name)
return config.get("model", "gpt-4.1")
def get_max_tokens(function_name: str) -> int:
"""Get max tokens for function"""
config = get_model_config(function_name)
return config.get("max_tokens", 4000)
def get_temperature(function_name: str) -> float:
"""Get temperature for function"""
config = get_model_config(function_name)
return config.get("temperature", 0.7)
```
**Key Changes:**
- These functions are redundant - `get_model_config()` already returns all needed values
- After removing MODEL_CONFIG, these functions become even less useful
- Code should call `get_model_config()` directly and extract values from the returned dict
---
### 7. `registry.py` - Keep Functions, Don't Export
**Current Issues:**
- `register_function()` is exported but never called (only `register_lazy_function` is used)
- `list_functions()` is exported but never called
**Changes Required:**
#### Keep Functions, Remove from Exports
```python
# KEEP THESE FUNCTIONS IN registry.py (may be useful for debugging/admin)
# BUT REMOVE FROM __init__.py exports
# In registry.py - KEEP:
def register_function(name: str, function_class: Type[BaseAIFunction]):
"""Register an AI function"""
# ... keep implementation
def list_functions() -> list:
"""List all registered functions"""
# ... keep implementation
# In __init__.py - REMOVE from exports:
# 'register_function', # ❌ Remove
# 'list_functions', # ❌ Remove
```
**Key Changes:**
- Keep functions in `registry.py` (may be useful for future direct registration or debugging)
- Remove from `__init__.py` exports (not used anywhere)
- If needed in future, can be imported directly from `registry.py`
---
## Error Handling Strategy
### New Error Messages
When IntegrationSettings not configured:
```python
ValueError: "OpenAI IntegrationSettings not configured for account {account_id}. Please configure OpenAI settings in the integration page."
```
When model not set in IntegrationSettings:
```python
ValueError: "Model not configured in IntegrationSettings for account {account_id}. Please set 'model' in OpenAI integration settings."
```
When account not provided:
```python
ValueError: "Account is required for model configuration"
```
### Frontend Impact
The frontend should handle these errors gracefully:
- Show user-friendly error message
- Redirect to integration settings page
- Provide clear instructions on how to configure
---
## Testing Plan
### Unit Tests
1. **Test `get_model_config()` with valid IntegrationSettings**
- Should return model from IntegrationSettings
- Should include max_tokens and temperature
2. **Test `get_model_config()` without IntegrationSettings**
- Should raise ValueError with clear message
3. **Test `get_model_config()` without model in config**
- Should raise ValueError with clear message
4. **Test `get_model_config()` without account**
- Should raise ValueError
5. **Test `ai_core.run_ai_request()` without model**
- Should raise ValueError
### Integration Tests
1. **Test each AI function with valid IntegrationSettings**
- `auto_cluster` - should work
- `generate_ideas` - should work
- `generate_content` - should work
- `generate_image_prompts` - should work
- `generate_images` - should work
2. **Test each AI function without IntegrationSettings**
- Should raise clear error
- Error should be user-friendly
3. **Test from view endpoints**
- Should return proper error response
- Should include request_id
- Should follow unified API format
---
## Migration Steps
### Step 1: Backup Current Code
- Create git branch: `refactor/remove-model-fallbacks`
- Commit current state
### Step 2: Update `settings.py`
- Remove `MODEL_CONFIG` dict
- Update `get_model_config()` function
- Add proper error handling
### Step 3: Update `ai_core.py`
- Remove `_default_model` initialization
- Update `run_ai_request()` to require model
- Add error handling
### Step 4: Update `engine.py`
- Validate account before calling `get_model_config()`
- Add error handling
### Step 5: Update `tasks.py`
- Validate account_id is provided
- Add error handling
### Step 6: Clean Up Orphan Code
- Remove orphan exports from `__init__.py`
- Remove unused helper functions from `settings.py`
- Remove unused imports from `__init__.py`
- Verify no broken imports
### Step 7: Update Tests
- Add unit tests for new error cases
- Update integration tests
- Test all 5 active functions
### Step 8: Test Manually
- Test each AI function with valid IntegrationSettings
- Test each AI function without IntegrationSettings
- Verify error messages are clear
### Step 9: Update Documentation
- Update AI framework docs
- Document new error handling
- Update API documentation
### Step 10: Deploy
- Deploy to staging
- Test in staging environment
- Deploy to production
---
## Rollback Plan
If issues arise:
1. **Immediate Rollback**
- Revert git branch
- Restore previous version
- Monitor for issues
2. **Partial Rollback**
- Keep IntegrationSettings changes
- Restore MODEL_CONFIG as fallback temporarily
- Fix issues incrementally
---
## Success Criteria
- [ ] All 5 active AI functions work with IntegrationSettings only
- [ ] Clear error messages when IntegrationSettings not configured
- [ ] No hardcoded model defaults remain
- [ ] No Django settings fallbacks remain
- [ ] Orphan code removed (orphan exports, unused functions)
- [ ] No broken imports after cleanup
- [ ] All tests pass
- [ ] Documentation updated
- [ ] Frontend handles errors gracefully
---
## Notes
- **IntegrationSettings Structure:** Each account must have `IntegrationSettings` with `integration_type='openai'` and `config` containing `model`, `max_tokens`, `temperature`
- **Account-Specific:** Each account configures their own models - no global defaults
- **Error Clarity:** All errors must be user-friendly and actionable
- **No Breaking Changes:** This is a refactoring, not a breaking change - existing accounts with IntegrationSettings will continue to work
---
**Last Updated:** 2025-01-XX
**Status:** Ready for Implementation

View File

@@ -3,7 +3,7 @@ IGNY8 AI Framework
Unified framework for all AI functions with consistent lifecycle, progress tracking, and logging. Unified framework for all AI functions with consistent lifecycle, progress tracking, and logging.
""" """
from igny8_core.ai.registry import register_function, get_function, list_functions from igny8_core.ai.registry import get_function
from igny8_core.ai.engine import AIEngine from igny8_core.ai.engine import AIEngine
from igny8_core.ai.base import BaseAIFunction from igny8_core.ai.base import BaseAIFunction
from igny8_core.ai.ai_core import AICore from igny8_core.ai.ai_core import AICore
@@ -27,11 +27,7 @@ from igny8_core.ai.constants import (
) )
from igny8_core.ai.prompts import PromptRegistry, get_prompt from igny8_core.ai.prompts import PromptRegistry, get_prompt
from igny8_core.ai.settings import ( from igny8_core.ai.settings import (
MODEL_CONFIG,
get_model_config, get_model_config,
get_model,
get_max_tokens,
get_temperature,
) )
# Don't auto-import functions here - let apps.py handle it lazily # Don't auto-import functions here - let apps.py handle it lazily
@@ -41,9 +37,7 @@ __all__ = [
'AIEngine', 'AIEngine',
'BaseAIFunction', 'BaseAIFunction',
'AICore', 'AICore',
'register_function',
'get_function', 'get_function',
'list_functions',
# Validators # Validators
'validate_ids', 'validate_ids',
'validate_keywords_exist', 'validate_keywords_exist',
@@ -64,10 +58,6 @@ __all__ = [
'PromptRegistry', 'PromptRegistry',
'get_prompt', 'get_prompt',
# Settings # Settings
'MODEL_CONFIG',
'get_model_config', 'get_model_config',
'get_model',
'get_max_tokens',
'get_temperature',
] ]

View File

@@ -40,7 +40,6 @@ class AICore:
self.account = account self.account = account
self._openai_api_key = None self._openai_api_key = None
self._runware_api_key = None self._runware_api_key = None
self._default_model = None
self._load_account_settings() self._load_account_settings()
def _load_account_settings(self): def _load_account_settings(self):
@@ -57,18 +56,6 @@ class AICore:
).first() ).first()
if openai_settings and openai_settings.config: if openai_settings and openai_settings.config:
self._openai_api_key = openai_settings.config.get('apiKey') self._openai_api_key = openai_settings.config.get('apiKey')
model = openai_settings.config.get('model')
if model:
if model in MODEL_RATES:
self._default_model = model
logger.info(f"Loaded model '{model}' from IntegrationSettings for account {self.account.id}")
else:
error_msg = f"Model '{model}' from IntegrationSettings is not in supported models list. Supported models: {list(MODEL_RATES.keys())}"
logger.error(f"[AICore] {error_msg}")
logger.error(f"[AICore] Account {self.account.id} has invalid model configuration. Please update Integration Settings.")
# Don't set _default_model, will fall back to Django settings
else:
logger.warning(f"No model configured in IntegrationSettings for account {self.account.id}, will use fallback")
# Load Runware settings # Load Runware settings
runware_settings = IntegrationSettings.objects.filter( runware_settings = IntegrationSettings.objects.filter(
@@ -81,13 +68,11 @@ class AICore:
except Exception as e: except Exception as e:
logger.warning(f"Could not load account settings: {e}", exc_info=True) logger.warning(f"Could not load account settings: {e}", exc_info=True)
# Fallback to Django settings # Fallback to Django settings for API keys only (no model fallback)
if not self._openai_api_key: if not self._openai_api_key:
self._openai_api_key = getattr(settings, 'OPENAI_API_KEY', None) self._openai_api_key = getattr(settings, 'OPENAI_API_KEY', None)
if not self._runware_api_key: if not self._runware_api_key:
self._runware_api_key = getattr(settings, 'RUNWARE_API_KEY', None) self._runware_api_key = getattr(settings, 'RUNWARE_API_KEY', None)
if not self._default_model:
self._default_model = getattr(settings, 'DEFAULT_AI_MODEL', DEFAULT_AI_MODEL)
def get_api_key(self, integration_type: str = 'openai') -> Optional[str]: def get_api_key(self, integration_type: str = 'openai') -> Optional[str]:
"""Get API key for integration type""" """Get API key for integration type"""
@@ -98,15 +83,20 @@ class AICore:
return None return None
def get_model(self, integration_type: str = 'openai') -> str: def get_model(self, integration_type: str = 'openai') -> str:
"""Get model for integration type""" """
if integration_type == 'openai': Get model for integration type.
return self._default_model DEPRECATED: Model should be passed directly to run_ai_request().
return DEFAULT_AI_MODEL This method is kept for backward compatibility but raises an error.
"""
raise ValueError(
"get_model() is deprecated. Model must be passed directly to run_ai_request(). "
"Use get_model_config() from settings.py to get model from IntegrationSettings."
)
def run_ai_request( def run_ai_request(
self, self,
prompt: str, prompt: str,
model: Optional[str] = None, model: str,
max_tokens: int = 4000, max_tokens: int = 4000,
temperature: float = 0.7, temperature: float = 0.7,
response_format: Optional[Dict] = None, response_format: Optional[Dict] = None,
@@ -121,7 +111,7 @@ class AICore:
Args: Args:
prompt: Prompt text prompt: Prompt text
model: Model name (defaults to account's default) model: Model name (required - must be provided from IntegrationSettings)
max_tokens: Maximum tokens max_tokens: Maximum tokens
temperature: Temperature (0-1) temperature: Temperature (0-1)
response_format: Optional response format dict (for JSON mode) response_format: Optional response format dict (for JSON mode)
@@ -132,6 +122,9 @@ class AICore:
Returns: Returns:
Dict with 'content', 'input_tokens', 'output_tokens', 'total_tokens', Dict with 'content', 'input_tokens', 'output_tokens', 'total_tokens',
'model', 'cost', 'error', 'api_id' 'model', 'cost', 'error', 'api_id'
Raises:
ValueError: If model is not provided
""" """
# Use provided tracker or create a new one # Use provided tracker or create a new one
if tracker is None: if tracker is None:
@@ -139,39 +132,11 @@ class AICore:
tracker.ai_call("Preparing request...") tracker.ai_call("Preparing request...")
# Step 1: Validate API key # Step 1: Validate model is provided
api_key = api_key or self._openai_api_key if not model:
if not api_key: error_msg = "Model is required. Ensure IntegrationSettings is configured for the account."
error_msg = 'OpenAI API key not configured'
tracker.error('ConfigurationError', error_msg) tracker.error('ConfigurationError', error_msg)
return {
'content': None,
'error': error_msg,
'input_tokens': 0,
'output_tokens': 0,
'total_tokens': 0,
'model': model or self._default_model,
'cost': 0.0,
'api_id': None,
}
# Step 2: Determine model
active_model = model or self._default_model
# Debug logging: Show model from settings vs model used
model_from_settings = self._default_model
model_used = active_model
logger.info(f"[AICore] Model Configuration Debug:")
logger.info(f" - Model from IntegrationSettings: {model_from_settings}")
logger.info(f" - Model parameter passed: {model}")
logger.info(f" - Model actually used in request: {model_used}")
tracker.ai_call(f"Model Debug - Settings: {model_from_settings}, Parameter: {model}, Using: {model_used}")
# Validate model is available and supported
if not active_model:
error_msg = 'No AI model configured. Please configure a model in Integration Settings or Django settings.'
logger.error(f"[AICore] {error_msg}") logger.error(f"[AICore] {error_msg}")
tracker.error('ConfigurationError', error_msg)
return { return {
'content': None, 'content': None,
'error': error_msg, 'error': error_msg,
@@ -183,6 +148,31 @@ class AICore:
'api_id': None, 'api_id': None,
} }
# Step 2: Validate API key
api_key = api_key or self._openai_api_key
if not api_key:
error_msg = 'OpenAI API key not configured'
tracker.error('ConfigurationError', error_msg)
return {
'content': None,
'error': error_msg,
'input_tokens': 0,
'output_tokens': 0,
'total_tokens': 0,
'model': model,
'cost': 0.0,
'api_id': None,
}
# Step 3: Use provided model (no fallback)
active_model = model
# Debug logging: Show model used
logger.info(f"[AICore] Model Configuration:")
logger.info(f" - Model parameter passed: {model}")
logger.info(f" - Model used in request: {active_model}")
tracker.ai_call(f"Using model: {active_model}")
if active_model not in MODEL_RATES: if active_model not in MODEL_RATES:
error_msg = f"Model '{active_model}' is not supported. Supported models: {list(MODEL_RATES.keys())}" error_msg = f"Model '{active_model}' is not supported. Supported models: {list(MODEL_RATES.keys())}"
logger.error(f"[AICore] {error_msg}") logger.error(f"[AICore] {error_msg}")

View File

@@ -193,6 +193,12 @@ class AIEngine:
self.tracker.update("PREP", 25, prep_message, meta=self.step_tracker.get_meta()) self.tracker.update("PREP", 25, prep_message, meta=self.step_tracker.get_meta())
# Phase 3: AI_CALL - Provider API Call (25-70%) # Phase 3: AI_CALL - Provider API Call (25-70%)
# Validate account exists before proceeding
if not self.account:
error_msg = "Account is required for AI function execution"
logger.error(f"[AIEngine] {error_msg}")
return self._handle_error(error_msg, fn)
ai_core = AICore(account=self.account) ai_core = AICore(account=self.account)
function_name = fn.get_name() function_name = fn.get_name()
@@ -201,29 +207,23 @@ class AIEngine:
function_id_base = function_name.replace('_', '-') function_id_base = function_name.replace('_', '-')
function_id = f"ai-{function_id_base}-01-desktop" function_id = f"ai-{function_id_base}-01-desktop"
# Get model config from settings (Stage 4 requirement) # Get model config from settings (requires account)
# Pass account to read model from IntegrationSettings # This will raise ValueError if IntegrationSettings not configured
try:
model_config = get_model_config(function_name, account=self.account) model_config = get_model_config(function_name, account=self.account)
model = model_config.get('model') model = model_config.get('model')
except ValueError as e:
# Read model straight from IntegrationSettings for visibility # IntegrationSettings not configured or model missing
model_from_integration = None error_msg = str(e)
if self.account: error_type = 'ConfigurationError'
try: logger.error(f"[AIEngine] {error_msg}")
from igny8_core.modules.system.models import IntegrationSettings return self._handle_error(error_msg, fn, error_type=error_type)
openai_settings = IntegrationSettings.objects.filter( except Exception as e:
integration_type='openai', # Other unexpected errors
account=self.account, error_msg = f"Failed to get model configuration: {str(e)}"
is_active=True error_type = type(e).__name__
).first() logger.error(f"[AIEngine] {error_msg}", exc_info=True)
if openai_settings and openai_settings.config: return self._handle_error(error_msg, fn, error_type=error_type)
model_from_integration = openai_settings.config.get('model')
except Exception as integration_error:
logger.warning(
"[AIEngine] Unable to read model from IntegrationSettings: %s",
integration_error,
exc_info=True,
)
# Debug logging: Show model configuration (console only, not in step tracker) # Debug logging: Show model configuration (console only, not in step tracker)
logger.info(f"[AIEngine] Model Configuration for {function_name}:") logger.info(f"[AIEngine] Model Configuration for {function_name}:")
@@ -375,18 +375,28 @@ class AIEngine:
} }
except Exception as e: except Exception as e:
logger.error(f"Error in AIEngine.execute for {function_name}: {str(e)}", exc_info=True) error_msg = str(e)
return self._handle_error(str(e), fn, exc_info=True) error_type = type(e).__name__
logger.error(f"Error in AIEngine.execute for {function_name}: {error_msg}", exc_info=True)
return self._handle_error(error_msg, fn, exc_info=True, error_type=error_type)
def _handle_error(self, error: str, fn: BaseAIFunction = None, exc_info=False): def _handle_error(self, error: str, fn: BaseAIFunction = None, exc_info=False, error_type: str = None):
"""Centralized error handling""" """Centralized error handling"""
function_name = fn.get_name() if fn else 'unknown' function_name = fn.get_name() if fn else 'unknown'
# Determine error type
if error_type:
final_error_type = error_type
elif isinstance(error, Exception):
final_error_type = type(error).__name__
else:
final_error_type = 'Error'
self.step_tracker.add_request_step("Error", "error", error, error=error) self.step_tracker.add_request_step("Error", "error", error, error=error)
error_meta = { error_meta = {
'error': error, 'error': error,
'error_type': type(error).__name__ if isinstance(error, Exception) else 'Error', 'error_type': final_error_type,
**self.step_tracker.get_meta() **self.step_tracker.get_meta()
} }
self.tracker.error(error, meta=error_meta) self.tracker.error(error, meta=error_meta)
@@ -401,7 +411,7 @@ class AIEngine:
return { return {
'success': False, 'success': False,
'error': error, 'error': error,
'error_type': type(error).__name__ if isinstance(error, Exception) else 'Error', 'error_type': final_error_type,
'request_steps': self.step_tracker.request_steps, 'request_steps': self.step_tracker.request_steps,
'response_steps': self.step_tracker.response_steps 'response_steps': self.step_tracker.response_steps
} }

View File

@@ -122,8 +122,10 @@ class GenerateImagesFunction(BaseAIFunction):
} }
) )
# Get model config # Get model config (requires account)
model_config = get_model_config('extract_image_prompts') if not account_obj:
raise ValueError("Account is required for model configuration")
model_config = get_model_config('extract_image_prompts', account=account_obj)
# Call AI to extract prompts using centralized request handler # Call AI to extract prompts using centralized request handler
result = ai_core.run_ai_request( result = ai_core.run_ai_request(

View File

@@ -1,46 +1,11 @@
""" """
AI Settings - Centralized model configurations and limits AI Settings - Centralized model configurations and limits
Uses IntegrationSettings only - no hardcoded defaults or fallbacks.
""" """
from typing import Dict, Any from typing import Dict, Any
import logging
# Model configurations for each AI function logger = logging.getLogger(__name__)
MODEL_CONFIG = {
"auto_cluster": {
"model": "gpt-4o-mini",
"max_tokens": 3000,
"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"}, # JSON output
},
"generate_content": {
"model": "gpt-4.1",
"max_tokens": 8000,
"temperature": 0.7,
"response_format": {"type": "json_object"}, # JSON output
},
"generate_images": {
"model": "dall-e-3",
"size": "1024x1024",
"provider": "openai",
},
"extract_image_prompts": {
"model": "gpt-4o-mini",
"max_tokens": 1000,
"temperature": 0.7,
"response_format": {"type": "json_object"},
},
"generate_image_prompts": {
"model": "gpt-4o-mini",
"max_tokens": 2000,
"temperature": 0.7,
"response_format": {"type": "json_object"},
},
}
# Function name aliases (for backward compatibility) # Function name aliases (for backward compatibility)
FUNCTION_ALIASES = { FUNCTION_ALIASES = {
@@ -52,71 +17,81 @@ FUNCTION_ALIASES = {
} }
def get_model_config(function_name: str, account=None) -> Dict[str, Any]: def get_model_config(function_name: str, account) -> Dict[str, Any]:
""" """
Get model configuration for an AI function. Get model configuration from IntegrationSettings only.
Reads model from IntegrationSettings if account is provided, otherwise uses defaults. No fallbacks - account must have IntegrationSettings configured.
Args: Args:
function_name: AI function name (e.g., 'auto_cluster', 'generate_ideas') function_name: Name of the AI function
account: Optional account object to read model from IntegrationSettings account: Account instance (required)
Returns: Returns:
Dict with model, max_tokens, temperature, etc. dict: Model configuration with 'model', 'max_tokens', 'temperature'
Raises:
ValueError: If account not provided or IntegrationSettings not configured
""" """
# Check aliases first if not account:
raise ValueError("Account is required for model configuration")
# Resolve function alias
actual_name = FUNCTION_ALIASES.get(function_name, function_name) actual_name = FUNCTION_ALIASES.get(function_name, function_name)
# Get base config # Get IntegrationSettings for OpenAI
config = MODEL_CONFIG.get(actual_name, {}).copy()
# Try to get model from IntegrationSettings if account is provided
model_from_settings = None
if account:
try: try:
from igny8_core.modules.system.models import IntegrationSettings from igny8_core.modules.system.models import IntegrationSettings
openai_settings = IntegrationSettings.objects.filter( integration_settings = IntegrationSettings.objects.get(
integration_type='openai', integration_type='openai',
account=account, account=account,
is_active=True is_active=True
).first() )
if openai_settings and openai_settings.config: except IntegrationSettings.DoesNotExist:
model_from_settings = openai_settings.config.get('model') raise ValueError(
if model_from_settings: f"OpenAI IntegrationSettings not configured for account {account.id}. "
# Validate model is in our supported list f"Please configure OpenAI settings in the integration page."
from igny8_core.utils.ai_processor import MODEL_RATES )
if model_from_settings in MODEL_RATES:
config['model'] = model_from_settings
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.warning(f"Could not load model from IntegrationSettings: {e}", exc_info=True)
# Merge with defaults config = integration_settings.config or {}
default_config = {
"model": "gpt-4.1", # Get model from config
"max_tokens": 4000, model = config.get('model')
"temperature": 0.7, if not model:
"response_format": None, raise ValueError(
f"Model not configured in IntegrationSettings for account {account.id}. "
f"Please set 'model' in OpenAI integration settings."
)
# Validate model is in our supported list (optional validation)
try:
from igny8_core.utils.ai_processor import MODEL_RATES
if model not in MODEL_RATES:
logger.warning(
f"Model '{model}' for account {account.id} is not in supported list. "
f"Supported models: {list(MODEL_RATES.keys())}"
)
except ImportError:
# MODEL_RATES not available - skip validation
pass
# Get max_tokens and temperature from config (with reasonable defaults for API)
max_tokens = config.get('max_tokens', 4000) # Reasonable default for API limits
temperature = config.get('temperature', 0.7) # Reasonable default
# Build response format based on model (JSON mode for supported models)
response_format = None
try:
from igny8_core.ai.constants import JSON_MODE_MODELS
if model in JSON_MODE_MODELS:
response_format = {"type": "json_object"}
except ImportError:
# JSON_MODE_MODELS not available - skip
pass
return {
'model': model,
'max_tokens': max_tokens,
'temperature': temperature,
'response_format': response_format,
} }
return {**default_config, **config}
def get_model(function_name: str) -> str:
"""Get model name for function"""
config = get_model_config(function_name)
return config.get("model", "gpt-4.1")
def get_max_tokens(function_name: str) -> int:
"""Get max tokens for function"""
config = get_model_config(function_name)
return config.get("max_tokens", 4000)
def get_temperature(function_name: str) -> float:
"""Get temperature for function"""
config = get_model_config(function_name)
return config.get("temperature", 0.7)

View File

@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
@shared_task(bind=True, max_retries=3) @shared_task(bind=True, max_retries=3)
def run_ai_task(self, function_name: str, payload: dict, account_id: int = None): def run_ai_task(self, function_name: str, payload: dict, account_id: int):
""" """
Single Celery entrypoint for all AI functions. Single Celery entrypoint for all AI functions.
Dynamically loads and executes the requested function. Dynamically loads and executes the requested function.
@@ -18,7 +18,10 @@ def run_ai_task(self, function_name: str, payload: dict, account_id: int = None)
Args: Args:
function_name: Name of the AI function (e.g., 'auto_cluster') function_name: Name of the AI function (e.g., 'auto_cluster')
payload: Function-specific payload payload: Function-specific payload
account_id: Account ID for account isolation account_id: Account ID for account isolation (required)
Raises:
Returns error dict if account_id not provided or account not found
""" """
logger.info("=" * 80) logger.info("=" * 80)
logger.info(f"run_ai_task STARTED: {function_name}") logger.info(f"run_ai_task STARTED: {function_name}")
@@ -29,14 +32,28 @@ def run_ai_task(self, function_name: str, payload: dict, account_id: int = None)
logger.info("=" * 80) logger.info("=" * 80)
try: try:
# Get account # Validate account_id is provided
account = None if not account_id:
if account_id: error_msg = "account_id is required for AI task execution"
logger.error(f"[run_ai_task] {error_msg}")
return {
'success': False,
'error': error_msg,
'error_type': 'ConfigurationError'
}
# Get account and validate it exists
from igny8_core.auth.models import Account from igny8_core.auth.models import Account
try: try:
account = Account.objects.get(id=account_id) account = Account.objects.get(id=account_id)
except Account.DoesNotExist: except Account.DoesNotExist:
logger.warning(f"Account {account_id} not found") error_msg = f"Account {account_id} not found"
logger.error(f"[run_ai_task] {error_msg}")
return {
'success': False,
'error': error_msg,
'error_type': 'AccountNotFound'
}
# Get function from registry # Get function from registry
fn = get_function_instance(function_name) fn = get_function_instance(function_name)

View File

@@ -0,0 +1,232 @@
"""
Unit tests for AI framework
Tests get_model_config() and AICore.run_ai_request() functions
"""
from django.test import TestCase
from igny8_core.auth.models import Account, User, Plan
from igny8_core.modules.system.models import IntegrationSettings
from igny8_core.ai.settings import get_model_config
from igny8_core.ai.ai_core import AICore
class GetModelConfigTestCase(TestCase):
"""Test cases for get_model_config() function"""
def setUp(self):
"""Set up test data"""
# Create plan first
self.plan = Plan.objects.create(
name="Test Plan",
slug="test-plan",
price=0,
credits_per_month=1000
)
# Create user first (Account needs owner)
self.user = User.objects.create_user(
username='testuser',
email='test@test.com',
password='testpass123',
role='owner'
)
# Create account with owner
self.account = Account.objects.create(
name='Test Account',
slug='test-account',
plan=self.plan,
owner=self.user,
status='active'
)
# Update user to have account
self.user.account = self.account
self.user.save()
def test_get_model_config_with_valid_settings(self):
"""Test get_model_config() with valid IntegrationSettings"""
IntegrationSettings.objects.create(
integration_type='openai',
account=self.account,
is_active=True,
config={
'model': 'gpt-4o',
'max_tokens': 4000,
'temperature': 0.7,
'apiKey': 'test-key'
}
)
config = get_model_config('auto_cluster', self.account)
self.assertEqual(config['model'], 'gpt-4o')
self.assertEqual(config['max_tokens'], 4000)
self.assertEqual(config['temperature'], 0.7)
self.assertIn('response_format', config)
def test_get_model_config_without_account(self):
"""Test get_model_config() without account - should raise ValueError"""
with self.assertRaises(ValueError) as context:
get_model_config('auto_cluster', None)
self.assertIn('Account is required', str(context.exception))
def test_get_model_config_without_integration_settings(self):
"""Test get_model_config() without IntegrationSettings - should raise ValueError"""
with self.assertRaises(ValueError) as context:
get_model_config('auto_cluster', self.account)
self.assertIn('OpenAI IntegrationSettings not configured', str(context.exception))
self.assertIn(str(self.account.id), str(context.exception))
def test_get_model_config_without_model_in_config(self):
"""Test get_model_config() without model in config - should raise ValueError"""
IntegrationSettings.objects.create(
integration_type='openai',
account=self.account,
is_active=True,
config={
'max_tokens': 4000,
'temperature': 0.7,
'apiKey': 'test-key'
# No 'model' key
}
)
with self.assertRaises(ValueError) as context:
get_model_config('auto_cluster', self.account)
self.assertIn('Model not configured in IntegrationSettings', str(context.exception))
self.assertIn(str(self.account.id), str(context.exception))
def test_get_model_config_with_inactive_settings(self):
"""Test get_model_config() with inactive IntegrationSettings - should raise ValueError"""
IntegrationSettings.objects.create(
integration_type='openai',
account=self.account,
is_active=False,
config={
'model': 'gpt-4o',
'max_tokens': 4000,
'temperature': 0.7
}
)
with self.assertRaises(ValueError) as context:
get_model_config('auto_cluster', self.account)
self.assertIn('OpenAI IntegrationSettings not configured', str(context.exception))
def test_get_model_config_with_function_alias(self):
"""Test get_model_config() with function alias"""
IntegrationSettings.objects.create(
integration_type='openai',
account=self.account,
is_active=True,
config={
'model': 'gpt-4o-mini',
'max_tokens': 2000,
'temperature': 0.5
}
)
# Test with alias
config1 = get_model_config('cluster_keywords', self.account)
config2 = get_model_config('auto_cluster', self.account)
# Both should return the same config
self.assertEqual(config1['model'], config2['model'])
self.assertEqual(config1['model'], 'gpt-4o-mini')
def test_get_model_config_json_mode_models(self):
"""Test get_model_config() sets response_format for JSON mode models"""
json_models = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo-preview']
for model in json_models:
IntegrationSettings.objects.filter(account=self.account).delete()
IntegrationSettings.objects.create(
integration_type='openai',
account=self.account,
is_active=True,
config={
'model': model,
'max_tokens': 4000,
'temperature': 0.7
}
)
config = get_model_config('auto_cluster', self.account)
self.assertIn('response_format', config)
self.assertEqual(config['response_format'], {'type': 'json_object'})
class AICoreTestCase(TestCase):
"""Test cases for AICore.run_ai_request() function"""
def setUp(self):
"""Set up test data"""
# Create plan first
self.plan = Plan.objects.create(
name="Test Plan",
slug="test-plan",
price=0,
credits_per_month=1000
)
# Create user first (Account needs owner)
self.user = User.objects.create_user(
username='testuser',
email='test@test.com',
password='testpass123',
role='owner'
)
# Create account with owner
self.account = Account.objects.create(
name='Test Account',
slug='test-account',
plan=self.plan,
owner=self.user,
status='active'
)
# Update user to have account
self.user.account = self.account
self.user.save()
self.ai_core = AICore(account=self.account)
def test_run_ai_request_without_model(self):
"""Test run_ai_request() without model - should return error dict"""
result = self.ai_core.run_ai_request(
prompt="Test prompt",
model=None,
function_name='test_function'
)
self.assertIn('error', result)
self.assertIn('Model is required', result['error'])
self.assertEqual(result['content'], None)
self.assertEqual(result['total_tokens'], 0)
def test_run_ai_request_with_empty_model(self):
"""Test run_ai_request() with empty model string - should return error dict"""
result = self.ai_core.run_ai_request(
prompt="Test prompt",
model="",
function_name='test_function'
)
self.assertIn('error', result)
self.assertIn('Model is required', result['error'])
self.assertEqual(result['content'], None)
self.assertEqual(result['total_tokens'], 0)
def test_get_model_deprecated(self):
"""Test get_model() method is deprecated and raises ValueError"""
with self.assertRaises(ValueError) as context:
self.ai_core.get_model('openai')
self.assertIn('deprecated', str(context.exception).lower())
self.assertIn('run_ai_request', str(context.exception))

View File

@@ -663,17 +663,47 @@ class ImagesViewSet(SiteSectorModelViewSet):
account = getattr(request, 'account', None) account = getattr(request, 'account', None)
# Get site_id and sector_id from query parameters
site_id = request.query_params.get('site_id')
sector_id = request.query_params.get('sector_id')
# Get all content that has images (either directly or via task) # Get all content that has images (either directly or via task)
# First, get content with direct image links # First, get content with direct image links
queryset = Content.objects.filter(images__isnull=False) queryset = Content.objects.filter(images__isnull=False)
if account: if account:
queryset = queryset.filter(account=account) queryset = queryset.filter(account=account)
# Apply site/sector filtering if provided
if site_id:
try:
queryset = queryset.filter(site_id=int(site_id))
except (ValueError, TypeError):
pass
if sector_id:
try:
queryset = queryset.filter(sector_id=int(sector_id))
except (ValueError, TypeError):
pass
# Also get content from images linked via task # Also get content from images linked via task
task_linked_images = Images.objects.filter(task__isnull=False, content__isnull=True) task_linked_images = Images.objects.filter(task__isnull=False, content__isnull=True)
if account: if account:
task_linked_images = task_linked_images.filter(account=account) task_linked_images = task_linked_images.filter(account=account)
# Apply site/sector filtering to task-linked images
if site_id:
try:
task_linked_images = task_linked_images.filter(site_id=int(site_id))
except (ValueError, TypeError):
pass
if sector_id:
try:
task_linked_images = task_linked_images.filter(sector_id=int(sector_id))
except (ValueError, TypeError):
pass
# Get content IDs from task-linked images # Get content IDs from task-linked images
task_content_ids = set() task_content_ids = set()
for image in task_linked_images: for image in task_linked_images:
@@ -694,6 +724,7 @@ class ImagesViewSet(SiteSectorModelViewSet):
for content_id in content_ids: for content_id in content_ids:
try: try:
content = Content.objects.get(id=content_id) content = Content.objects.get(id=content_id)
# Get images linked directly to content OR via task # Get images linked directly to content OR via task
content_images = Images.objects.filter( content_images = Images.objects.filter(
Q(content=content) | Q(task=content.task) Q(content=content) | Q(task=content.task)

View File

@@ -114,12 +114,11 @@ The IGNY8 AI framework provides a unified interface for all AI operations. All A
#### Model Settings #### Model Settings
**File**: `backend/igny8_core/ai/settings.py` **File**: `backend/igny8_core/ai/settings.py`
**Constants**: `MODEL_CONFIG` - Model configurations per function (model, max_tokens, temperature, response_format) **Constants**: `FUNCTION_ALIASES` - Function name aliases for backward compatibility
**Functions**: **Functions**:
- `get_model_config` - Gets model config for function (reads from IntegrationSettings if account provided) - `get_model_config(function_name, account)` - Gets model config from IntegrationSettings (account required, no fallbacks)
- `get_model` - Gets model name for function - Raises `ValueError` if IntegrationSettings not configured
- `get_max_tokens` - Gets max tokens for function - Returns dict with `model`, `max_tokens`, `temperature`, `response_format`
- `get_temperature` - Gets temperature for function
--- ---
@@ -442,36 +441,87 @@ All AI functions follow the same 6-phase execution:
## Model Configuration ## Model Configuration
### Model Settings ### IntegrationSettings - Single Source of Truth
**Default Models**: **IMPORTANT**: As of the refactoring completed in 2025-01-XX, the AI framework uses **IntegrationSettings only** for model configuration. There are no hardcoded defaults or fallbacks.
- Clustering: `gpt-4o-mini`
- Ideas: `gpt-4o-mini`
- Content: `gpt-4o`
- Image Prompts: `gpt-4o-mini`
- Images: `dall-e-3` (OpenAI) or `runware:97@1` (Runware)
### Per-Account Override
**IntegrationSettings Model**: **IntegrationSettings Model**:
- `integration_type`: 'openai' or 'runware' - `integration_type`: 'openai' or 'runware' (required)
- `config`: JSONField with model configuration - `account`: Account instance (required) - each account must configure their own models
- `model`: Model name - `is_active`: Boolean (must be True for configuration to be used)
- `max_tokens`: Max tokens - `config`: JSONField with model configuration (required)
- `temperature`: Temperature - `model`: Model name (required) - e.g., 'gpt-4o-mini', 'gpt-4o', 'dall-e-3'
- `response_format`: Response format - `max_tokens`: Max tokens (optional, defaults to 4000)
- `temperature`: Temperature (optional, defaults to 0.7)
- `response_format`: Response format (optional, automatically set for JSON mode models)
### Model Configuration ### Model Configuration Function
**File**: `backend/igny8_core/ai/settings.py` **File**: `backend/igny8_core/ai/settings.py`
**MODEL_CONFIG**: Dictionary mapping function names to model configurations **Function**: `get_model_config(function_name: str, account) -> Dict[str, Any]`
**Functions**: **Behavior**:
- `get_model_config(function_name, account=None)`: Gets model config (checks IntegrationSettings if account provided) - **Requires** `account` parameter (no longer optional)
- `get_model(function_name, account=None)`: Gets model name - **Requires** IntegrationSettings to be configured for the account
- `get_max_tokens(function_name, account=None)`: Gets max tokens - **Raises** `ValueError` with clear error messages if:
- `get_temperature(function_name, account=None)`: Gets temperature - Account not provided
- IntegrationSettings not found for account
- Model not configured in IntegrationSettings
- IntegrationSettings is inactive
**Error Messages**:
- Missing account: `"Account is required for model configuration"`
- Missing IntegrationSettings: `"OpenAI IntegrationSettings not configured for account {id}. Please configure OpenAI settings in the integration page."`
- Missing model: `"Model not configured in IntegrationSettings for account {id}. Please set 'model' in OpenAI integration settings."`
**Returns**:
```python
{
'model': str, # Model name from IntegrationSettings
'max_tokens': int, # From config or default 4000
'temperature': float, # From config or default 0.7
'response_format': dict, # JSON mode for supported models, or None
}
```
### Account-Specific Configuration
**Key Principle**: Each account must configure their own AI models. There are no global defaults.
**Configuration Steps**:
1. Navigate to Settings → Integrations
2. Configure OpenAI integration settings
3. Set `model` in the configuration (required)
4. Optionally set `max_tokens` and `temperature`
5. Ensure integration is active
**Supported Models**:
- Text generation: `gpt-4o-mini`, `gpt-4o`, `gpt-4-turbo`, etc.
- Image generation: `dall-e-3` (OpenAI) or `runware:97@1` (Runware)
- JSON mode: Automatically enabled for supported models (gpt-4o, gpt-4-turbo, etc.)
### Function Aliases
**File**: `backend/igny8_core/ai/settings.py`
**FUNCTION_ALIASES**: Dictionary mapping legacy function names to current names
- `cluster_keywords``auto_cluster`
- `auto_cluster_keywords``auto_cluster`
- `auto_generate_ideas``generate_ideas`
- `auto_generate_content``generate_content`
- `auto_generate_images``generate_images`
**Purpose**: Maintains backward compatibility with legacy function names.
### Removed Functions
The following helper functions were removed as part of the refactoring (they were never used):
- `get_model()` - Removed (use `get_model_config()['model']` instead)
- `get_max_tokens()` - Removed (use `get_model_config()['max_tokens']` instead)
- `get_temperature()` - Removed (use `get_model_config()['temperature']` instead)
**Rationale**: These functions were redundant - `get_model_config()` already returns all needed values.
--- ---

File diff suppressed because it is too large Load Diff

View File

@@ -1,545 +0,0 @@
# IGNY8 API Documentation v1.0
**Base URL**: `https://api.igny8.com/api/v1/`
**Version**: 1.0.0
**Last Updated**: 2025-11-16
## Quick Links
- [Interactive API Documentation (Swagger UI)](#swagger-ui)
- [Authentication Guide](#authentication)
- [Response Format](#response-format)
- [Error Handling](#error-handling)
- [Rate Limiting](#rate-limiting)
- [Pagination](#pagination)
- [Endpoint Reference](#endpoint-reference)
---
## Swagger UI
Interactive API documentation is available at:
- **Swagger UI**: `https://api.igny8.com/api/docs/`
- **ReDoc**: `https://api.igny8.com/api/redoc/`
- **OpenAPI Schema**: `https://api.igny8.com/api/schema/`
The Swagger UI provides:
- Interactive endpoint testing
- Request/response examples
- Authentication testing
- Schema definitions
- Code samples in multiple languages
---
## Authentication
### JWT Bearer Token
All endpoints require JWT Bearer token authentication except:
- `POST /api/v1/auth/login/` - User login
- `POST /api/v1/auth/register/` - User registration
### Getting an Access Token
**Login Endpoint:**
```http
POST /api/v1/auth/login/
Content-Type: application/json
{
"email": "user@example.com",
"password": "your_password"
}
```
**Response:**
```json
{
"success": true,
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"username": "user",
"role": "owner"
},
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"request_id": "uuid"
}
```
### Using the Token
Include the token in the `Authorization` header:
```http
GET /api/v1/planner/keywords/
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```
### Token Expiration
- **Access Token**: 15 minutes
- **Refresh Token**: 7 days
Use the refresh token to get a new access token:
```http
POST /api/v1/auth/refresh/
Content-Type: application/json
{
"refresh": "your_refresh_token"
}
```
---
## Response Format
### Success Response
All successful responses follow this unified format:
```json
{
"success": true,
"data": {
"id": 1,
"name": "Example",
...
},
"message": "Optional success message",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
### Paginated Response
List endpoints return paginated data:
```json
{
"success": true,
"count": 100,
"next": "https://api.igny8.com/api/v1/planner/keywords/?page=2",
"previous": null,
"results": [
{"id": 1, "name": "Keyword 1"},
{"id": 2, "name": "Keyword 2"},
...
],
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
### Error Response
All error responses follow this unified format:
```json
{
"success": false,
"error": "Validation failed",
"errors": {
"email": ["This field is required"],
"password": ["Password too short"]
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
---
## Error Handling
### HTTP Status Codes
| Code | Meaning | Description |
|------|---------|-------------|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 204 | No Content | Resource deleted successfully |
| 400 | Bad Request | Validation error or invalid request |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | Permission denied |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Resource conflict (e.g., duplicate) |
| 422 | Unprocessable Entity | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error |
### Error Response Structure
All errors include:
- `success`: Always `false`
- `error`: Top-level error message
- `errors`: Field-specific errors (for validation errors)
- `request_id`: Unique request ID for debugging
### Example Error Responses
**Validation Error (400):**
```json
{
"success": false,
"error": "Validation failed",
"errors": {
"email": ["Invalid email format"],
"password": ["Password must be at least 8 characters"]
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
**Authentication Error (401):**
```json
{
"success": false,
"error": "Authentication required",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
**Permission Error (403):**
```json
{
"success": false,
"error": "Permission denied",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
**Not Found (404):**
```json
{
"success": false,
"error": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
**Rate Limit (429):**
```json
{
"success": false,
"error": "Rate limit exceeded",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
---
## Rate Limiting
Rate limits are scoped by operation type. Check response headers for limit information:
- `X-Throttle-Limit`: Maximum requests allowed
- `X-Throttle-Remaining`: Remaining requests in current window
- `X-Throttle-Reset`: Time when limit resets (Unix timestamp)
### Rate Limit Scopes
| Scope | Limit | Description |
|-------|-------|-------------|
| `ai_function` | 10/min | AI content generation, clustering |
| `image_gen` | 15/min | Image generation |
| `content_write` | 30/min | Content creation, updates |
| `content_read` | 100/min | Content listing, retrieval |
| `auth` | 20/min | Login, register, password reset |
| `auth_strict` | 5/min | Sensitive auth operations |
| `planner` | 60/min | Keyword, cluster, idea operations |
| `planner_ai` | 10/min | AI-powered planner operations |
| `writer` | 60/min | Task, content management |
| `writer_ai` | 10/min | AI-powered writer operations |
| `system` | 100/min | Settings, prompts, profiles |
| `system_admin` | 30/min | Admin-only system operations |
| `billing` | 30/min | Credit queries, usage logs |
| `billing_admin` | 10/min | Credit management (admin) |
| `default` | 100/min | Default for endpoints without scope |
### Handling Rate Limits
When rate limited (429), the response includes:
- Error message: "Rate limit exceeded"
- Headers with reset time
- Wait until `X-Throttle-Reset` before retrying
**Example:**
```http
HTTP/1.1 429 Too Many Requests
X-Throttle-Limit: 60
X-Throttle-Remaining: 0
X-Throttle-Reset: 1700123456
{
"success": false,
"error": "Rate limit exceeded",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
---
## Pagination
List endpoints support pagination with query parameters:
- `page`: Page number (default: 1)
- `page_size`: Items per page (default: 10, max: 100)
### Example Request
```http
GET /api/v1/planner/keywords/?page=2&page_size=20
```
### Paginated Response
```json
{
"success": true,
"count": 100,
"next": "https://api.igny8.com/api/v1/planner/keywords/?page=3&page_size=20",
"previous": "https://api.igny8.com/api/v1/planner/keywords/?page=1&page_size=20",
"results": [
{"id": 21, "name": "Keyword 21"},
{"id": 22, "name": "Keyword 22"},
...
],
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
### Pagination Fields
- `count`: Total number of items
- `next`: URL to next page (null if last page)
- `previous`: URL to previous page (null if first page)
- `results`: Array of items for current page
---
## Endpoint Reference
### Authentication Endpoints
#### Login
```http
POST /api/v1/auth/login/
```
#### Register
```http
POST /api/v1/auth/register/
```
#### Refresh Token
```http
POST /api/v1/auth/refresh/
```
### Planner Endpoints
#### List Keywords
```http
GET /api/v1/planner/keywords/
```
#### Create Keyword
```http
POST /api/v1/planner/keywords/
```
#### Get Keyword
```http
GET /api/v1/planner/keywords/{id}/
```
#### Update Keyword
```http
PUT /api/v1/planner/keywords/{id}/
PATCH /api/v1/planner/keywords/{id}/
```
#### Delete Keyword
```http
DELETE /api/v1/planner/keywords/{id}/
```
#### Auto Cluster Keywords
```http
POST /api/v1/planner/keywords/auto_cluster/
```
### Writer Endpoints
#### List Tasks
```http
GET /api/v1/writer/tasks/
```
#### Create Task
```http
POST /api/v1/writer/tasks/
```
### System Endpoints
#### System Status
```http
GET /api/v1/system/status/
```
#### List Prompts
```http
GET /api/v1/system/prompts/
```
### Billing Endpoints
#### Credit Balance
```http
GET /api/v1/billing/credits/balance/balance/
```
#### Usage Summary
```http
GET /api/v1/billing/credits/usage/summary/
```
---
## Code Examples
### Python
```python
import requests
BASE_URL = "https://api.igny8.com/api/v1"
# Login
response = requests.post(
f"{BASE_URL}/auth/login/",
json={"email": "user@example.com", "password": "password"}
)
data = response.json()
if data['success']:
token = data['data']['access']
# Use token for authenticated requests
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
# Get keywords
response = requests.get(
f"{BASE_URL}/planner/keywords/",
headers=headers
)
keywords_data = response.json()
if keywords_data['success']:
keywords = keywords_data['results']
print(f"Found {keywords_data['count']} keywords")
else:
print(f"Error: {keywords_data['error']}")
else:
print(f"Login failed: {data['error']}")
```
### JavaScript
```javascript
const BASE_URL = 'https://api.igny8.com/api/v1';
// Login
const loginResponse = await fetch(`${BASE_URL}/auth/login/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com',
password: 'password'
})
});
const loginData = await loginResponse.json();
if (loginData.success) {
const token = loginData.data.access;
// Use token for authenticated requests
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
// Get keywords
const keywordsResponse = await fetch(
`${BASE_URL}/planner/keywords/`,
{ headers }
);
const keywordsData = await keywordsResponse.json();
if (keywordsData.success) {
const keywords = keywordsData.results;
console.log(`Found ${keywordsData.count} keywords`);
} else {
console.error('Error:', keywordsData.error);
}
} else {
console.error('Login failed:', loginData.error);
}
```
### cURL
```bash
# Login
curl -X POST https://api.igny8.com/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}'
# Get keywords (with token)
curl -X GET https://api.igny8.com/api/v1/planner/keywords/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json"
```
---
## Request ID
Every API request includes a unique `request_id` in the response. Use this ID for:
- Debugging issues
- Log correlation
- Support requests
The `request_id` is included in:
- All success responses
- All error responses
- Response headers (`X-Request-ID`)
---
## Support
For API support:
- Check the [Interactive Documentation](https://api.igny8.com/api/docs/)
- Review [Error Codes Reference](ERROR-CODES.md)
- Contact support with your `request_id`
---
**Last Updated**: 2025-11-16
**API Version**: 1.0.0

View File

@@ -1,210 +0,0 @@
# Documentation Implementation Summary
**Section 2: Documentation - COMPLETE**
**Date Completed**: 2025-11-16
**Last Updated**: 2025-01-XX
**Status**: All Documentation Complete and Ready
**API Standard v1.0**: ✅ **100% COMPLIANT** - All remaining items completed
---
## Implementation Overview
Complete documentation system for IGNY8 API v1.0 including:
- OpenAPI 3.0 schema generation
- Interactive Swagger UI
- Comprehensive documentation files
- Code examples and integration guides
---
## OpenAPI/Swagger Integration ✅
### Configuration
- ✅ Installed `drf-spectacular>=0.27.0`
- ✅ Added to `INSTALLED_APPS`
- ✅ Configured `SPECTACULAR_SETTINGS` with comprehensive description
- ✅ Added URL endpoints for schema and documentation
### Endpoints Created
-`/api/schema/` - OpenAPI 3.0 schema (JSON/YAML)
-`/api/docs/` - Swagger UI (interactive documentation)
-`/api/redoc/` - ReDoc (alternative documentation UI)
### Features
- ✅ Comprehensive API description with features overview
- ✅ Authentication documentation (JWT Bearer tokens)
- ✅ Response format examples
- ✅ Rate limiting documentation
- ✅ Pagination documentation
- ✅ Endpoint tags (Authentication, Planner, Writer, System, Billing)
- ✅ Code samples in Python and JavaScript
- ✅ Custom authentication extensions
---
## Documentation Files Created ✅
### 1. API-DOCUMENTATION.md
**Purpose**: Complete API reference
**Contents**:
- Quick start guide
- Authentication guide
- Response format details
- Error handling
- Rate limiting
- Pagination
- Endpoint reference
- Code examples (Python, JavaScript, cURL)
### 2. AUTHENTICATION-GUIDE.md
**Purpose**: Authentication and authorization
**Contents**:
- JWT Bearer token authentication
- Token management and refresh
- Code examples (Python, JavaScript)
- Security best practices
- Token expiration handling
- Troubleshooting
### 3. ERROR-CODES.md
**Purpose**: Complete error code reference
**Contents**:
- HTTP status codes (200, 201, 400, 401, 403, 404, 409, 422, 429, 500)
- Field-specific error messages
- Error handling best practices
- Common error scenarios
- Debugging tips
### 4. RATE-LIMITING.md
**Purpose**: Rate limiting and throttling
**Contents**:
- Rate limit scopes and limits
- Handling rate limits (429 responses)
- Best practices
- Code examples with backoff strategies
- Request queuing and caching
### 5. MIGRATION-GUIDE.md
**Purpose**: Migration guide for API consumers
**Contents**:
- What changed in v1.0
- Step-by-step migration instructions
- Code examples (before/after)
- Breaking and non-breaking changes
- Migration checklist
### 6. WORDPRESS-PLUGIN-INTEGRATION.md
**Purpose**: WordPress plugin integration
**Contents**:
- Complete PHP API client class
- Authentication implementation
- Error handling
- WordPress admin integration
- Best practices
- Testing examples
### 7. README.md
**Purpose**: Documentation index
**Contents**:
- Documentation index
- Quick start guide
- Links to all documentation files
- Support information
---
## Schema Extensions ✅
### Custom Authentication Extensions
-`JWTAuthenticationExtension` - JWT Bearer token authentication
-`CSRFExemptSessionAuthenticationExtension` - Session authentication
- ✅ Proper OpenAPI security scheme definitions
**File**: `backend/igny8_core/api/schema_extensions.py`
---
## Verification
### Schema Generation
```bash
python manage.py spectacular --color
```
**Status**: ✅ Schema generates successfully
### Documentation Endpoints
-`/api/schema/` - OpenAPI schema
-`/api/docs/` - Swagger UI
-`/api/redoc/` - ReDoc
### Documentation Files
- ✅ 7 comprehensive documentation files created
- ✅ All files include code examples
- ✅ All files include best practices
- ✅ All files properly formatted
---
## Documentation Statistics
- **Total Documentation Files**: 7
- **Total Pages**: ~100+ pages of documentation
- **Code Examples**: Python, JavaScript, PHP, cURL
- **Coverage**: 100% of API features documented
---
## What's Documented
### ✅ API Features
- Unified response format
- Authentication and authorization
- Error handling
- Rate limiting
- Pagination
- Request ID tracking
### ✅ Integration Guides
- Python integration
- JavaScript integration
- WordPress plugin integration
- Migration from legacy format
### ✅ Reference Materials
- Error codes
- Rate limit scopes
- Endpoint reference
- Code examples
---
## Access Points
### Interactive Documentation
- **Swagger UI**: `https://api.igny8.com/api/docs/`
- **ReDoc**: `https://api.igny8.com/api/redoc/`
- **OpenAPI Schema**: `https://api.igny8.com/api/schema/`
### Documentation Files
- All files in `docs/` directory
- Index: `docs/README.md`
---
## Next Steps
1. ✅ Documentation complete
2. ✅ Swagger UI accessible
3. ✅ All guides created
4. ✅ Changelog updated
**Section 2: Documentation is COMPLETE**
---
**Last Updated**: 2025-11-16
**API Version**: 1.0.0

View File

@@ -1,113 +0,0 @@
# IGNY8 API Documentation
**Version**: 1.0.0
**Last Updated**: 2025-11-16
Complete documentation for the IGNY8 Unified API Standard v1.0.
---
## Documentation Index
### Getting Started
1. **[API Documentation](API-DOCUMENTATION.md)** - Complete API reference with examples
- Quick start guide
- Endpoint reference
- Code examples (Python, JavaScript, cURL)
- Response format details
2. **[Authentication Guide](AUTHENTICATION-GUIDE.md)** - Authentication and authorization
- JWT Bearer token authentication
- Token management
- Code examples
- Security best practices
3. **[Error Codes Reference](ERROR-CODES.md)** - Complete error code reference
- HTTP status codes
- Field-specific errors
- Error handling best practices
- Common error scenarios
4. **[Rate Limiting Guide](RATE-LIMITING.md)** - Rate limiting and throttling
- Rate limit scopes
- Handling rate limits
- Best practices
- Code examples
### Integration Guides
5. **[Migration Guide](MIGRATION-GUIDE.md)** - Migrating to API v1.0
- What changed
- Step-by-step migration
- Code examples
- Breaking changes
6. **[WordPress Plugin Integration](WORDPRESS-PLUGIN-INTEGRATION.md)** - WordPress integration
- PHP API client
- Authentication
- Error handling
- Best practices
### Interactive Documentation
- **Swagger UI**: `https://api.igny8.com/api/docs/`
- **ReDoc**: `https://api.igny8.com/api/redoc/`
- **OpenAPI Schema**: `https://api.igny8.com/api/schema/`
---
## Quick Start
### 1. Get Access Token
```bash
curl -X POST https://api.igny8.com/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}'
```
### 2. Use Token
```bash
curl -X GET https://api.igny8.com/api/v1/planner/keywords/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json"
```
### 3. Handle Response
All responses follow unified format:
```json
{
"success": true,
"data": {...},
"request_id": "uuid"
}
```
---
## API Standard Features
-**Unified Response Format** - Consistent JSON structure
-**Layered Authorization** - Authentication → Tenant → Role → Site/Sector
-**Centralized Error Handling** - All errors in unified format
-**Scoped Rate Limiting** - Different limits per operation type
-**Tenant Isolation** - Account/site/sector scoping
-**Request Tracking** - Unique request ID for debugging
---
## Support
- **Interactive Docs**: [Swagger UI](https://api.igny8.com/api/docs/)
- **Error Reference**: [Error Codes](ERROR-CODES.md)
- **Contact**: Include `request_id` from responses when contacting support
---
**Last Updated**: 2025-11-16
**API Version**: 1.0.0

View File

@@ -136,6 +136,11 @@ export default function ResourceDebugOverlay({ enabled }: ResourceDebugOverlayPr
credentials: 'include', // Include session cookies for authentication credentials: 'include', // Include session cookies for authentication
}); });
// Silently ignore 404s - metrics endpoint might not exist for all requests
if (response.status === 404) {
return; // Don't log or retry 404s
}
if (response.ok) { if (response.ok) {
const responseData = await response.json(); const responseData = await response.json();
// Extract data from unified API response format: {success: true, data: {...}} // Extract data from unified API response format: {success: true, data: {...}}

View File

@@ -153,6 +153,24 @@ export default function Images() {
loadImages(); loadImages();
}, [loadImages]); }, [loadImages]);
// Listen for site and sector changes and refresh data
useEffect(() => {
const handleSiteChange = () => {
loadImages();
};
const handleSectorChange = () => {
loadImages();
};
window.addEventListener('siteChanged', handleSiteChange);
window.addEventListener('sectorChanged', handleSectorChange);
return () => {
window.removeEventListener('siteChanged', handleSiteChange);
window.removeEventListener('sectorChanged', handleSectorChange);
};
}, [loadImages]);
// Debounced search // Debounced search
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
@@ -258,7 +276,7 @@ export default function Images() {
type: 'in_article', type: 'in_article',
position: img.position || idx + 1, position: img.position || idx + 1,
contentTitle: contentImages.content_title || `Content #${contentId}`, contentTitle: contentImages.content_title || `Content #${contentId}`,
prompt: img.prompt, prompt: img.prompt || undefined,
status: 'pending', status: 'pending',
progress: 0, progress: 0,
imageUrl: null, imageUrl: null,

View File

@@ -193,13 +193,13 @@ export const useAuthStore = create<AuthState>()(
} }
try { try {
// Use unified API system - fetchAPI automatically handles auth token from store // Use fetchAPI which handles token automatically and extracts data from unified format
// fetchAPI is already imported at the top of the file
const response = await fetchAPI('/v1/auth/me/'); const response = await fetchAPI('/v1/auth/me/');
// fetchAPI extracts data from unified format {success: true, data: {user: {...}}} // fetchAPI extracts data field, so response is {user: {...}}
// So response is {user: {...}}
if (!response || !response.user) { if (!response || !response.user) {
throw new Error('Invalid user data received'); throw new Error('Failed to refresh user data');
} }
// Update user data with latest from server // Update user data with latest from server
@@ -209,7 +209,7 @@ export const useAuthStore = create<AuthState>()(
// If refresh fails, don't logout - just log the error // If refresh fails, don't logout - just log the error
// User might still be authenticated, just couldn't refresh data // User might still be authenticated, just couldn't refresh data
console.warn('Failed to refresh user data:', error); console.warn('Failed to refresh user data:', error);
throw new Error(error.message || 'Failed to refresh user data'); // Don't throw - just log the warning to prevent error accumulation
} }
}, },
}), }),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff