Enhance image size configuration and integration settings. Default image sizes are now set based on provider and model, with options for featured, desktop, and mobile images. Updated frontend to allow selectable image sizes in settings.
This commit is contained in:
@@ -185,8 +185,9 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
|||||||
image_format = config.get('image_format', 'webp')
|
image_format = config.get('image_format', 'webp')
|
||||||
desktop_enabled = config.get('desktop_enabled', True)
|
desktop_enabled = config.get('desktop_enabled', True)
|
||||||
mobile_enabled = config.get('mobile_enabled', True)
|
mobile_enabled = config.get('mobile_enabled', True)
|
||||||
# Determine featured image size based on provider
|
# Get image sizes from config, with fallback defaults
|
||||||
featured_image_size = '1280x832' if provider == 'runware' else '1024x1024'
|
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"[process_image_generation_queue] Settings loaded:")
|
||||||
logger.info(f" - Provider: {provider}")
|
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
|
# Use appropriate size based on image type
|
||||||
image_size = featured_image_size if image.image_type == 'featured' else '1024x1024'
|
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(
|
result = ai_core.generate_image(
|
||||||
prompt=formatted_prompt,
|
prompt=formatted_prompt,
|
||||||
|
|||||||
@@ -618,6 +618,19 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
config.setdefault('desktop_enabled', True)
|
config.setdefault('desktop_enabled', True)
|
||||||
config.setdefault('mobile_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
|
# Get or create integration settings
|
||||||
logger.info(f"[save_settings] Attempting get_or_create for {integration_type} with account {account.id}")
|
logger.info(f"[save_settings] Attempting get_or_create for {integration_type} with account {account.id}")
|
||||||
integration_settings, created = IntegrationSettings.objects.get_or_create(
|
integration_settings, created = IntegrationSettings.objects.get_or_create(
|
||||||
@@ -746,6 +759,10 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
# Get model - try 'model' first, then 'imageModel' as fallback
|
# Get model - try 'model' first, then 'imageModel' as fallback
|
||||||
model = config.get('model') or config.get('imageModel') or 'dall-e-3'
|
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({
|
return Response({
|
||||||
'success': True,
|
'success': True,
|
||||||
'config': {
|
'config': {
|
||||||
@@ -756,6 +773,8 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
'image_format': config.get('image_format', 'webp'),
|
'image_format': config.get('image_format', 'webp'),
|
||||||
'desktop_enabled': config.get('desktop_enabled', True),
|
'desktop_enabled': config.get('desktop_enabled', True),
|
||||||
'mobile_enabled': config.get('mobile_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)
|
}, status=status.HTTP_200_OK)
|
||||||
except IntegrationSettings.DoesNotExist:
|
except IntegrationSettings.DoesNotExist:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Suspense, lazy } from "react";
|
import { Suspense, lazy } from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router";
|
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router";
|
||||||
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
import AppLayout from "./layout/AppLayout";
|
import AppLayout from "./layout/AppLayout";
|
||||||
import { ScrollToTop } from "./components/common/ScrollToTop";
|
import { ScrollToTop } from "./components/common/ScrollToTop";
|
||||||
import ProtectedRoute from "./components/auth/ProtectedRoute";
|
import ProtectedRoute from "./components/auth/ProtectedRoute";
|
||||||
@@ -106,6 +107,7 @@ export default function App() {
|
|||||||
<GlobalErrorDisplay />
|
<GlobalErrorDisplay />
|
||||||
<LoadingStateMonitor />
|
<LoadingStateMonitor />
|
||||||
<Router>
|
<Router>
|
||||||
|
<HelmetProvider>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Auth Routes - Public */}
|
{/* Auth Routes - Public */}
|
||||||
@@ -486,6 +488,7 @@ export default function App() {
|
|||||||
{/* Fallback Route */}
|
{/* Fallback Route */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</HelmetProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import "./styles/igny8-colors.css"; /* IGNY8 custom colors - separate from TailA
|
|||||||
import "swiper/swiper-bundle.css";
|
import "swiper/swiper-bundle.css";
|
||||||
import "flatpickr/dist/flatpickr.css";
|
import "flatpickr/dist/flatpickr.css";
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import { AppWrapper } from "./components/common/PageMeta.tsx";
|
|
||||||
import { ThemeProvider } from "./context/ThemeContext.tsx";
|
import { ThemeProvider } from "./context/ThemeContext.tsx";
|
||||||
import { ToastProvider } from "./components/ui/toast/ToastContainer.tsx";
|
import { ToastProvider } from "./components/ui/toast/ToastContainer.tsx";
|
||||||
import { HeaderMetricsProvider } from "./context/HeaderMetricsContext.tsx";
|
import { HeaderMetricsProvider } from "./context/HeaderMetricsContext.tsx";
|
||||||
@@ -17,9 +16,7 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<HeaderMetricsProvider>
|
<HeaderMetricsProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<AppWrapper>
|
|
||||||
<App />
|
<App />
|
||||||
</AppWrapper>
|
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</HeaderMetricsProvider>
|
</HeaderMetricsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ interface IntegrationConfig {
|
|||||||
image_format?: string; // 'webp', 'jpg', 'png'
|
image_format?: string; // 'webp', 'jpg', 'png'
|
||||||
desktop_enabled?: boolean;
|
desktop_enabled?: boolean;
|
||||||
mobile_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() {
|
export default function Integration() {
|
||||||
@@ -91,6 +93,8 @@ export default function Integration() {
|
|||||||
image_format: 'webp', // 'webp', 'jpg', 'png'
|
image_format: 'webp', // 'webp', 'jpg', 'png'
|
||||||
desktop_enabled: true,
|
desktop_enabled: true,
|
||||||
mobile_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
|
// For image_generation, map service to provider and ensure all settings are included
|
||||||
let configToSave = { ...config };
|
let configToSave = { ...config };
|
||||||
if (selectedIntegration === 'image_generation') {
|
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 = {
|
configToSave = {
|
||||||
...config,
|
...config,
|
||||||
provider: config.service || config.provider || 'openai', // Map service to provider for backend
|
provider: config.service || config.provider || 'openai', // Map service to provider for backend
|
||||||
// Ensure model is set correctly based on service
|
// 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
|
// Ensure all image settings have defaults
|
||||||
image_type: config.image_type || 'realistic',
|
image_type: config.image_type || 'realistic',
|
||||||
max_in_article_images: config.max_in_article_images || 2,
|
max_in_article_images: config.max_in_article_images || 2,
|
||||||
image_format: config.image_format || 'webp',
|
image_format: config.image_format || 'webp',
|
||||||
desktop_enabled: config.desktop_enabled !== undefined ? config.desktop_enabled : true,
|
desktop_enabled: config.desktop_enabled !== undefined ? config.desktop_enabled : true,
|
||||||
mobile_enabled: config.mobile_enabled !== undefined ? config.mobile_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 [];
|
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 getSettingsFields = useCallback((integrationId: string): FormField[] => {
|
||||||
const config = integrations[integrationId];
|
const config = integrations[integrationId];
|
||||||
|
|
||||||
@@ -666,6 +706,41 @@ export default function Integration() {
|
|||||||
return [];
|
return [];
|
||||||
}, [integrations]);
|
}, [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
|
// Memoize custom body for image generation modal to prevent infinite loops
|
||||||
const imageGenerationCustomBody = useMemo(() => {
|
const imageGenerationCustomBody = useMemo(() => {
|
||||||
if (selectedIntegration !== 'image_generation' || !showSettingsModal) return undefined;
|
if (selectedIntegration !== 'image_generation' || !showSettingsModal) return undefined;
|
||||||
@@ -726,24 +801,38 @@ export default function Integration() {
|
|||||||
Max Images
|
Max Images
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{/* Featured Image (full width) */}
|
{/* Featured Image (full width) - Selectable */}
|
||||||
<div className="p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gradient-to-r from-purple-500 to-blue-500 text-white">
|
<div className="p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gradient-to-r from-purple-500 to-blue-500 text-white">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div>
|
|
||||||
<div className="font-medium">Featured Image</div>
|
<div className="font-medium">Featured Image</div>
|
||||||
<div className="text-xs opacity-90">1280×832 pixels</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs bg-white/20 px-2 py-1 rounded">
|
<div className="text-xs bg-white/20 px-2 py-1 rounded">
|
||||||
Always Enabled
|
Always Enabled
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-full [&_.igny8-select-styled]:bg-white/10 [&_.igny8-select-styled]:border-white/20 [&_.igny8-select-styled]:text-white [&_.igny8-select-styled]:placeholder:text-white/70 [&_.igny8-select-styled]:focus:border-white/40 [&_.igny8-select-styled]:focus:ring-white/20">
|
||||||
|
<SelectDropdown
|
||||||
|
options={getImageSizes(service, service === 'openai' ? (integrations[selectedIntegration]?.model || 'dall-e-3') : (integrations[selectedIntegration]?.runwareModel || 'runware:97@1'))}
|
||||||
|
value={integrations[selectedIntegration]?.featured_image_size || '1024x1024'}
|
||||||
|
onChange={(value) => {
|
||||||
|
setIntegrations({
|
||||||
|
...integrations,
|
||||||
|
[selectedIntegration]: {
|
||||||
|
...integrations[selectedIntegration],
|
||||||
|
featured_image_size: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Row 2: Desktop & Mobile Images (2 columns) */}
|
{/* Row 2: Desktop & Mobile Images (2 columns) */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{/* Desktop Images Checkbox */}
|
{/* Desktop Images Checkbox with Size Selector */}
|
||||||
<div className="flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700">
|
<div className="p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={integrations[selectedIntegration]?.desktop_enabled !== false}
|
checked={integrations[selectedIntegration]?.desktop_enabled !== false}
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
@@ -756,17 +845,29 @@ export default function Integration() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div>
|
|
||||||
<Label className="font-medium text-gray-700 dark:text-gray-300">
|
<Label className="font-medium text-gray-700 dark:text-gray-300">
|
||||||
Desktop Images
|
Desktop Images
|
||||||
</Label>
|
</Label>
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
1024×1024 pixels
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{integrations[selectedIntegration]?.desktop_enabled !== false && (
|
||||||
|
<SelectDropdown
|
||||||
|
options={getImageSizes(service, service === 'openai' ? (integrations[selectedIntegration]?.model || 'dall-e-3') : (integrations[selectedIntegration]?.runwareModel || 'runware:97@1'))}
|
||||||
|
value={integrations[selectedIntegration]?.desktop_image_size || '1024x1024'}
|
||||||
|
onChange={(value) => {
|
||||||
|
setIntegrations({
|
||||||
|
...integrations,
|
||||||
|
[selectedIntegration]: {
|
||||||
|
...integrations[selectedIntegration],
|
||||||
|
desktop_image_size: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Images Checkbox */}
|
{/* Mobile Images Checkbox - Fixed to 512x512 */}
|
||||||
<div className="flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700">
|
<div className="flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={integrations[selectedIntegration]?.mobile_enabled !== false}
|
checked={integrations[selectedIntegration]?.mobile_enabled !== false}
|
||||||
@@ -785,7 +886,7 @@ export default function Integration() {
|
|||||||
Mobile Images
|
Mobile Images
|
||||||
</Label>
|
</Label>
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
960×1280 pixels
|
512×512 pixels
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user