diff --git a/backend/igny8_core/ai/tasks.py b/backend/igny8_core/ai/tasks.py index 3c5fbd02..7b99992c 100644 --- a/backend/igny8_core/ai/tasks.py +++ b/backend/igny8_core/ai/tasks.py @@ -185,8 +185,9 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None image_format = config.get('image_format', 'webp') desktop_enabled = config.get('desktop_enabled', True) mobile_enabled = config.get('mobile_enabled', True) - # Determine featured image size based on provider - featured_image_size = '1280x832' if provider == 'runware' else '1024x1024' + # Get image sizes from config, with fallback defaults + featured_image_size = config.get('featured_image_size') or ('1280x832' if provider == 'runware' else '1024x1024') + desktop_image_size = config.get('desktop_image_size') or '1024x1024' logger.info(f"[process_image_generation_queue] Settings loaded:") logger.info(f" - Provider: {provider}") @@ -457,8 +458,15 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None } ) - # Use featured size for featured images, default for others - image_size = featured_image_size if image.image_type == 'featured' else '1024x1024' + # Use appropriate size based on image type + if image.image_type == 'featured': + image_size = featured_image_size + elif image.image_type == 'desktop': + image_size = desktop_image_size + elif image.image_type == 'mobile': + image_size = '512x512' # Fixed mobile size + else: # in_article or other + image_size = '1024x1024' # Default for in-article images result = ai_core.generate_image( prompt=formatted_prompt, diff --git a/backend/igny8_core/modules/system/integration_views.py b/backend/igny8_core/modules/system/integration_views.py index fbcc85bf..ce09ea1a 100644 --- a/backend/igny8_core/modules/system/integration_views.py +++ b/backend/igny8_core/modules/system/integration_views.py @@ -617,6 +617,19 @@ class IntegrationSettingsViewSet(viewsets.ViewSet): config.setdefault('image_format', 'webp') config.setdefault('desktop_enabled', True) config.setdefault('mobile_enabled', True) + + # Set default image sizes based on provider/model + provider = config.get('provider', 'openai') + model = config.get('model', 'dall-e-3') + + if not config.get('featured_image_size'): + if provider == 'runware': + config['featured_image_size'] = '1280x832' + else: # openai + config['featured_image_size'] = '1024x1024' + + if not config.get('desktop_image_size'): + config['desktop_image_size'] = '1024x1024' # Get or create integration settings logger.info(f"[save_settings] Attempting get_or_create for {integration_type} with account {account.id}") @@ -746,6 +759,10 @@ class IntegrationSettingsViewSet(viewsets.ViewSet): # Get model - try 'model' first, then 'imageModel' as fallback model = config.get('model') or config.get('imageModel') or 'dall-e-3' + # Set defaults for image sizes if not present + provider = config.get('provider', 'openai') + default_featured_size = '1280x832' if provider == 'runware' else '1024x1024' + return Response({ 'success': True, 'config': { @@ -756,6 +773,8 @@ class IntegrationSettingsViewSet(viewsets.ViewSet): 'image_format': config.get('image_format', 'webp'), 'desktop_enabled': config.get('desktop_enabled', True), 'mobile_enabled': config.get('mobile_enabled', True), + 'featured_image_size': config.get('featured_image_size', default_featured_size), + 'desktop_image_size': config.get('desktop_image_size', '1024x1024'), } }, status=status.HTTP_200_OK) except IntegrationSettings.DoesNotExist: diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 04ec4ef9..c1e44265 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import { Suspense, lazy } from "react"; import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router"; +import { HelmetProvider } from "react-helmet-async"; import AppLayout from "./layout/AppLayout"; import { ScrollToTop } from "./components/common/ScrollToTop"; import ProtectedRoute from "./components/auth/ProtectedRoute"; @@ -106,8 +107,9 @@ export default function App() { - - + + + {/* Auth Routes - Public */} } /> } /> @@ -486,6 +488,7 @@ export default function App() { {/* Fallback Route */} } /> + ); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2c6f003c..be1ae153 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,7 +5,6 @@ import "./styles/igny8-colors.css"; /* IGNY8 custom colors - separate from TailA import "swiper/swiper-bundle.css"; import "flatpickr/dist/flatpickr.css"; import App from "./App.tsx"; -import { AppWrapper } from "./components/common/PageMeta.tsx"; import { ThemeProvider } from "./context/ThemeContext.tsx"; import { ToastProvider } from "./components/ui/toast/ToastContainer.tsx"; import { HeaderMetricsProvider } from "./context/HeaderMetricsContext.tsx"; @@ -17,9 +16,7 @@ createRoot(document.getElementById("root")!).render( - - - + diff --git a/frontend/src/pages/Settings/Integration.tsx b/frontend/src/pages/Settings/Integration.tsx index b5619da6..3a151cef 100644 --- a/frontend/src/pages/Settings/Integration.tsx +++ b/frontend/src/pages/Settings/Integration.tsx @@ -63,6 +63,8 @@ interface IntegrationConfig { image_format?: string; // 'webp', 'jpg', 'png' desktop_enabled?: boolean; mobile_enabled?: boolean; + featured_image_size?: string; // e.g., '1280x832', '1024x1024' + desktop_image_size?: string; // e.g., '1024x1024', '512x512' } export default function Integration() { @@ -91,6 +93,8 @@ export default function Integration() { image_format: 'webp', // 'webp', 'jpg', 'png' desktop_enabled: true, mobile_enabled: true, + featured_image_size: '1024x1024', // Default, will be set based on provider/model + desktop_image_size: '1024x1024', // Default, will be set based on provider/model }, }); @@ -382,17 +386,26 @@ export default function Integration() { // For image_generation, map service to provider and ensure all settings are included let configToSave = { ...config }; if (selectedIntegration === 'image_generation') { + // Determine default sizes based on provider/model + const currentService = config.service || 'openai'; + const currentModel = currentService === 'openai' ? (config.model || 'dall-e-3') : (config.runwareModel || 'runware:97@1'); + const availableSizes = getImageSizes(currentService, currentModel); + const defaultFeaturedSize = availableSizes.length > 0 ? availableSizes[0].value : '1024x1024'; + const defaultDesktopSize = availableSizes.length > 0 ? availableSizes[0].value : '1024x1024'; + configToSave = { ...config, provider: config.service || config.provider || 'openai', // Map service to provider for backend // Ensure model is set correctly based on service - model: config.service === 'openai' ? (config.model || 'dall-e-3') : (config.service === 'runware' ? (config.runwareModel || 'runware:97@1') : config.model), + model: currentService === 'openai' ? (config.model || 'dall-e-3') : (currentService === 'runware' ? (config.runwareModel || 'runware:97@1') : config.model), // Ensure all image settings have defaults image_type: config.image_type || 'realistic', max_in_article_images: config.max_in_article_images || 2, image_format: config.image_format || 'webp', desktop_enabled: config.desktop_enabled !== undefined ? config.desktop_enabled : true, mobile_enabled: config.mobile_enabled !== undefined ? config.mobile_enabled : true, + featured_image_size: config.featured_image_size || defaultFeaturedSize, + desktop_image_size: config.desktop_image_size || defaultDesktopSize, }; } @@ -480,6 +493,33 @@ export default function Integration() { return []; }; + // Get available image sizes with prices based on provider and model + const getImageSizes = useCallback((provider: string, model: string) => { + if (provider === 'runware') { + return [ + { value: '1280x832', label: '1280×832 pixels - $0.009', price: 0.009 }, + { value: '1024x1024', label: '1024×1024 pixels - $0.009', price: 0.009 }, + { value: '512x512', label: '512×512 pixels - $0.006', price: 0.006 }, + ]; + } else if (provider === 'openai') { + if (model === 'dall-e-2') { + return [ + { value: '256x256', label: '256×256 pixels - $0.016', price: 0.016 }, + { value: '512x512', label: '512×512 pixels - $0.018', price: 0.018 }, + { value: '1024x1024', label: '1024×1024 pixels - $0.02', price: 0.02 }, + ]; + } else if (model === 'dall-e-3') { + return [ + { value: '1024x1024', label: '1024×1024 pixels - $0.04', price: 0.04 }, + ]; + } + } + // Default fallback + return [ + { value: '1024x1024', label: '1024×1024 pixels', price: 0 }, + ]; + }, []); + const getSettingsFields = useCallback((integrationId: string): FormField[] => { const config = integrations[integrationId]; @@ -666,6 +706,41 @@ export default function Integration() { return []; }, [integrations]); + // Update image sizes when service/model changes + useEffect(() => { + if (selectedIntegration !== 'image_generation' || !showSettingsModal) return; + + const config = integrations[selectedIntegration]; + if (!config) return; + + const service = config.service || 'openai'; + const model = service === 'openai' ? (config.model || 'dall-e-3') : (config.runwareModel || 'runware:97@1'); + const availableSizes = getImageSizes(service, model); + + if (availableSizes.length > 0) { + const defaultSize = availableSizes[0].value; + const currentFeaturedSize = config.featured_image_size; + const currentDesktopSize = config.desktop_image_size; + + // Check if current sizes are valid for the new provider/model + const validSizes = availableSizes.map(s => s.value); + const needsUpdate = + !currentFeaturedSize || !validSizes.includes(currentFeaturedSize) || + !currentDesktopSize || !validSizes.includes(currentDesktopSize); + + if (needsUpdate) { + setIntegrations({ + ...integrations, + [selectedIntegration]: { + ...config, + featured_image_size: validSizes.includes(currentFeaturedSize || '') ? currentFeaturedSize : defaultSize, + desktop_image_size: validSizes.includes(currentDesktopSize || '') ? currentDesktopSize : defaultSize, + }, + }); + } + } + }, [integrations[selectedIntegration]?.service, integrations[selectedIntegration]?.model, integrations[selectedIntegration]?.runwareModel, selectedIntegration, showSettingsModal, getImageSizes]); + // Memoize custom body for image generation modal to prevent infinite loops const imageGenerationCustomBody = useMemo(() => { if (selectedIntegration !== 'image_generation' || !showSettingsModal) return undefined; @@ -726,47 +801,73 @@ export default function Integration() { Max Images - {/* Featured Image (full width) */} + {/* Featured Image (full width) - Selectable */}
-
-
-
Featured Image
-
1280×832 pixels
-
+
+
Featured Image
Always Enabled
+
+ { + setIntegrations({ + ...integrations, + [selectedIntegration]: { + ...integrations[selectedIntegration], + featured_image_size: value, + }, + }); + }} + className="w-full" + /> +
{/* Row 2: Desktop & Mobile Images (2 columns) */}
- {/* Desktop Images Checkbox */} -
- { - setIntegrations({ - ...integrations, - [selectedIntegration]: { - ...integrations[selectedIntegration], - desktop_enabled: checked, - }, - }); - }} - /> -
+ {/* Desktop Images Checkbox with Size Selector */} +
+
+ { + setIntegrations({ + ...integrations, + [selectedIntegration]: { + ...integrations[selectedIntegration], + desktop_enabled: checked, + }, + }); + }} + /> -
- 1024×1024 pixels -
+ {integrations[selectedIntegration]?.desktop_enabled !== false && ( + { + setIntegrations({ + ...integrations, + [selectedIntegration]: { + ...integrations[selectedIntegration], + desktop_image_size: value, + }, + }); + }} + className="w-full" + /> + )}
- {/* Mobile Images Checkbox */} + {/* Mobile Images Checkbox - Fixed to 512x512 */}
- 960×1280 pixels + 512×512 pixels