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