image gen mess
This commit is contained in:
@@ -51,7 +51,9 @@ export const Modal: React.FC<ModalProps> = ({
|
||||
|
||||
const contentClasses = isFullscreen
|
||||
? "w-full h-full"
|
||||
: "relative w-full max-w-lg mx-4 rounded-3xl bg-white dark:bg-gray-900 shadow-xl";
|
||||
: className
|
||||
? `relative mx-4 rounded-3xl bg-white dark:bg-gray-900 shadow-xl ${className}`
|
||||
: "relative w-full max-w-lg mx-4 rounded-3xl bg-white dark:bg-gray-900 shadow-xl";
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center overflow-y-auto modal z-99999">
|
||||
@@ -63,7 +65,7 @@ export const Modal: React.FC<ModalProps> = ({
|
||||
)}
|
||||
<div
|
||||
ref={modalRef}
|
||||
className={`${contentClasses} ${className}`}
|
||||
className={contentClasses}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{showCloseButton && (
|
||||
|
||||
@@ -77,74 +77,94 @@ export default function SiteSettings() {
|
||||
const [contentGenerationSaving, setContentGenerationSaving] = useState(false);
|
||||
|
||||
// Image Settings state
|
||||
const [imageQuality, setImageQuality] = useState<'standard' | 'premium' | 'best'>('premium');
|
||||
const [imageQuality, setImageQuality] = useState<'basic' | 'quality' | 'premium'>('basic');
|
||||
const [imageSettings, setImageSettings] = useState({
|
||||
enabled: true,
|
||||
service: 'openai' as 'openai' | 'runware',
|
||||
provider: 'openai',
|
||||
model: 'dall-e-3',
|
||||
image_type: 'realistic' as 'realistic' | 'artistic' | 'cartoon',
|
||||
service: 'runware' as 'runware',
|
||||
provider: 'runware',
|
||||
model: 'runware:97@1',
|
||||
image_type: 'photorealistic' as string,
|
||||
max_in_article_images: 2,
|
||||
image_format: 'webp' as 'webp' | 'jpg' | 'png',
|
||||
featured_image_size: '1024x1024',
|
||||
});
|
||||
const [imageSettingsLoading, setImageSettingsLoading] = useState(false);
|
||||
const [imageSettingsSaving, setImageSettingsSaving] = useState(false);
|
||||
|
||||
// Image quality to config mapping
|
||||
// Updated to use new Runware models via API
|
||||
const QUALITY_TO_CONFIG: Record<string, { service: 'openai' | 'runware'; model: string }> = {
|
||||
standard: { service: 'openai', model: 'dall-e-2' },
|
||||
premium: { service: 'openai', model: 'dall-e-3' },
|
||||
best: { service: 'runware', model: 'runware:97@1' }, // Uses model-specific landscape size
|
||||
// Current image provider (from GlobalIntegrationSettings)
|
||||
const [imageProvider, setImageProvider] = useState<'runware' | 'openai'>('runware');
|
||||
|
||||
// Available style options loaded from backend (dynamic based on provider)
|
||||
const [availableStyles, setAvailableStyles] = useState<Array<{value: string; label: string; description?: string}>>([
|
||||
{ value: 'photorealistic', label: 'Photorealistic', description: 'Ultra realistic photography style' },
|
||||
]);
|
||||
|
||||
// Image quality to config mapping - Runware models only with model-specific sizes
|
||||
const QUALITY_TO_CONFIG: Record<string, { service: 'runware'; model: string }> = {
|
||||
basic: { service: 'runware', model: 'runware:97@1' }, // 6 credits/image
|
||||
quality: { service: 'runware', model: 'bria:10@1' }, // 10 credits/image
|
||||
premium: { service: 'runware', model: 'google:4@2' }, // 15 credits/image
|
||||
};
|
||||
|
||||
// Runware model choices with descriptions
|
||||
// Runware model choices with credits per image (from backend AIModelConfig)
|
||||
const RUNWARE_MODEL_CHOICES = [
|
||||
{ value: 'runware:97@1', label: 'Hi Dream Full - Basic', description: 'Fast & affordable' },
|
||||
{ value: 'bria:10@1', label: 'Bria 3.2 - Quality', description: 'Commercial-safe, licensed data' },
|
||||
{ value: 'google:4@2', label: 'Nano Banana - Premium', description: 'Best quality, text rendering' },
|
||||
{ value: 'runware:97@1', label: 'Basic (6 credits/image)', credits: 6 },
|
||||
{ value: 'bria:10@1', label: 'Quality (10 credits/image)', credits: 10 },
|
||||
{ value: 'google:4@2', label: 'Premium (15 credits/image)', credits: 15 },
|
||||
];
|
||||
|
||||
const getQualityFromConfig = (service?: string, model?: string): 'standard' | 'premium' | 'best' => {
|
||||
if (service === 'runware') return 'best';
|
||||
if (model === 'dall-e-3') return 'premium';
|
||||
return 'standard';
|
||||
// OpenAI DALL-E model choices
|
||||
const DALLE_MODEL_CHOICES = [
|
||||
{ value: 'dall-e-3', label: 'DALL-E 3 - HD Quality', credits: 40 },
|
||||
];
|
||||
|
||||
// Model-specific style options (image_type attribute compatible with each model)
|
||||
const getModelStyleOptions = (model: string) => {
|
||||
// Bria supports medium parameter (photography/art)
|
||||
if (model === 'bria:10@1') {
|
||||
return [
|
||||
{ value: 'photography', label: 'Photography' },
|
||||
{ value: 'art', label: 'Artistic' },
|
||||
];
|
||||
}
|
||||
// All models support these basic styles via prompt modification
|
||||
return [
|
||||
{ value: 'realistic', label: 'Realistic' },
|
||||
{ value: 'artistic', label: 'Artistic' },
|
||||
{ value: 'cartoon', label: 'Cartoon' },
|
||||
];
|
||||
};
|
||||
|
||||
const getImageSizes = (provider: string, model: string) => {
|
||||
if (provider === 'runware') {
|
||||
// Model-specific sizes - featured uses landscape, in-article alternates
|
||||
// Sizes shown are for featured image (landscape)
|
||||
return [
|
||||
{ value: '1280x768', label: '1280×768 (Landscape)' },
|
||||
{ value: '1024x1024', label: '1024×1024 (Square)' },
|
||||
];
|
||||
} else if (provider === 'openai') {
|
||||
if (model === 'dall-e-2') {
|
||||
return [
|
||||
{ value: '256x256', label: '256×256 pixels' },
|
||||
{ value: '512x512', label: '512×512 pixels' },
|
||||
{ value: '1024x1024', label: '1024×1024 pixels' },
|
||||
];
|
||||
} else if (model === 'dall-e-3') {
|
||||
return [
|
||||
{ value: '1024x1024', label: '1024×1024 pixels' },
|
||||
];
|
||||
}
|
||||
}
|
||||
return [{ value: '1024x1024', label: '1024×1024 pixels' }];
|
||||
const getQualityFromConfig = (service?: string, model?: string): 'basic' | 'quality' | 'premium' => {
|
||||
if (model === 'google:4@2') return 'premium';
|
||||
if (model === 'bria:10@1') return 'quality';
|
||||
return 'basic'; // Default to basic (runware:97@1)
|
||||
};
|
||||
|
||||
// Model-specific landscape sizes (used for featured image)
|
||||
const MODEL_LANDSCAPE_SIZES: Record<string, string> = {
|
||||
'runware:97@1': '1280x768', // Hi Dream Full
|
||||
'bria:10@1': '1344x768', // Bria 3.2
|
||||
'google:4@2': '1376x768', // Nano Banana
|
||||
'dall-e-3': '1792x1024', // DALL-E 3
|
||||
'dall-e-2': '1024x1024', // DALL-E 2 (square only)
|
||||
};
|
||||
|
||||
const getLandscapeSizeForModel = (model: string): string => {
|
||||
return MODEL_LANDSCAPE_SIZES[model] || (imageProvider === 'openai' ? '1792x1024' : '1280x768');
|
||||
};
|
||||
|
||||
const getCurrentImageConfig = useCallback(() => {
|
||||
// For OpenAI provider, return the DALL-E model directly
|
||||
if (imageProvider === 'openai') {
|
||||
return { service: 'openai', model: imageSettings.model || 'dall-e-3' };
|
||||
}
|
||||
// For Runware provider, use quality-based config
|
||||
const config = QUALITY_TO_CONFIG[imageQuality];
|
||||
return { service: config.service, model: config.model };
|
||||
}, [imageQuality]);
|
||||
}, [imageQuality, imageProvider, imageSettings.model]);
|
||||
|
||||
const availableImageSizes = getImageSizes(
|
||||
getCurrentImageConfig().service,
|
||||
getCurrentImageConfig().model
|
||||
);
|
||||
// Get the current model's landscape size for display
|
||||
const currentLandscapeSize = getLandscapeSizeForModel(imageSettings.model || getCurrentImageConfig().model);
|
||||
|
||||
// Sectors selection state
|
||||
const [industries, setIndustries] = useState<Industry[]>([]);
|
||||
@@ -228,31 +248,15 @@ export default function SiteSettings() {
|
||||
}
|
||||
}, [activeTab, siteId]);
|
||||
|
||||
// Update image sizes when quality changes
|
||||
// Update image config when quality changes (all Runware models)
|
||||
useEffect(() => {
|
||||
const config = getCurrentImageConfig();
|
||||
const sizes = getImageSizes(config.service, config.model);
|
||||
const defaultSize = sizes.length > 0 ? sizes[0].value : '1024x1024';
|
||||
|
||||
const validSizes = sizes.map(s => s.value);
|
||||
const needsFeaturedUpdate = !validSizes.includes(imageSettings.featured_image_size);
|
||||
|
||||
if (needsFeaturedUpdate) {
|
||||
setImageSettings(prev => ({
|
||||
...prev,
|
||||
service: config.service,
|
||||
provider: config.service,
|
||||
model: config.model,
|
||||
featured_image_size: needsFeaturedUpdate ? defaultSize : prev.featured_image_size,
|
||||
}));
|
||||
} else {
|
||||
setImageSettings(prev => ({
|
||||
...prev,
|
||||
service: config.service,
|
||||
provider: config.service,
|
||||
model: config.model,
|
||||
}));
|
||||
}
|
||||
setImageSettings(prev => ({
|
||||
...prev,
|
||||
service: config.service,
|
||||
provider: config.service,
|
||||
model: config.model,
|
||||
}));
|
||||
}, [imageQuality, getCurrentImageConfig]);
|
||||
|
||||
// Load sites for selector
|
||||
@@ -429,21 +433,69 @@ export default function SiteSettings() {
|
||||
const loadImageSettings = async () => {
|
||||
try {
|
||||
setImageSettingsLoading(true);
|
||||
const imageData = await fetchAPI('/v1/system/settings/integrations/image_generation/');
|
||||
if (imageData) {
|
||||
const quality = getQualityFromConfig(imageData.service || imageData.provider, imageData.model);
|
||||
const response = await fetchAPI('/v1/system/settings/integrations/image_generation/');
|
||||
// API returns { data: { config: {...} } } structure - try multiple paths
|
||||
const config = response?.data?.config || response?.config || response?.data || response || {};
|
||||
|
||||
console.log('[loadImageSettings] Raw response:', response);
|
||||
console.log('[loadImageSettings] Extracted config:', config);
|
||||
|
||||
if (config) {
|
||||
// Get provider from config (GlobalIntegrationSettings default_image_service)
|
||||
const provider = config.provider || config.service || 'runware';
|
||||
setImageProvider(provider as 'runware' | 'openai');
|
||||
|
||||
// Load available styles from backend (provider-specific)
|
||||
// If not available from API, use default fallbacks
|
||||
if (config.available_styles && Array.isArray(config.available_styles) && config.available_styles.length > 0) {
|
||||
setAvailableStyles(config.available_styles);
|
||||
console.log('[loadImageSettings] Loaded styles from API:', config.available_styles);
|
||||
} else {
|
||||
// Fallback styles if API doesn't return them (backward compatibility)
|
||||
const fallbackStyles = provider === 'openai'
|
||||
? [
|
||||
{ value: 'natural', label: 'Natural', description: 'More realistic, photographic style' },
|
||||
{ value: 'vivid', label: 'Vivid', description: 'Hyper-real, dramatic, artistic' },
|
||||
]
|
||||
: [
|
||||
{ value: 'photorealistic', label: 'Photorealistic', description: 'Ultra realistic photography style, natural lighting, real world look' },
|
||||
{ value: 'illustration', label: 'Illustration', description: 'Digital illustration, clean lines, artistic but not realistic' },
|
||||
{ value: '3d_render', label: '3D Render', description: 'Computer generated 3D style, modern, polished, depth and lighting' },
|
||||
{ value: 'minimal_flat', label: 'Minimal / Flat Design', description: 'Simple shapes, flat colors, modern UI and graphic design look' },
|
||||
{ value: 'artistic', label: 'Artistic / Painterly', description: 'Expressive, painted or hand drawn aesthetic' },
|
||||
{ value: 'cartoon', label: 'Cartoon / Stylized', description: 'Playful, exaggerated forms, animated or mascot style' },
|
||||
];
|
||||
setAvailableStyles(fallbackStyles);
|
||||
console.log('[loadImageSettings] Using fallback styles for provider:', provider);
|
||||
}
|
||||
|
||||
// Get model based on provider
|
||||
let loadedModel = config.model || 'runware:97@1';
|
||||
if (provider === 'openai') {
|
||||
loadedModel = config.model || 'dall-e-3';
|
||||
} else {
|
||||
loadedModel = config.model || config.runwareModel || 'runware:97@1';
|
||||
}
|
||||
|
||||
const quality = getQualityFromConfig(provider, loadedModel);
|
||||
setImageQuality(quality);
|
||||
|
||||
// Get image_type from config - map old values to new ones
|
||||
let imageType = config.image_type || (provider === 'openai' ? 'natural' : 'photorealistic');
|
||||
// Map old style values to new ones
|
||||
if (imageType === 'realistic') imageType = provider === 'openai' ? 'natural' : 'photorealistic';
|
||||
|
||||
setImageSettings({
|
||||
enabled: imageData.enabled !== false,
|
||||
service: imageData.service || imageData.provider || 'openai',
|
||||
provider: imageData.provider || imageData.service || 'openai',
|
||||
model: imageData.model || 'dall-e-3',
|
||||
image_type: imageData.image_type || 'realistic',
|
||||
max_in_article_images: imageData.max_in_article_images || 2,
|
||||
image_format: imageData.image_format || 'webp',
|
||||
featured_image_size: imageData.featured_image_size || '1024x1024',
|
||||
enabled: config.enabled !== false,
|
||||
service: provider,
|
||||
provider: provider,
|
||||
model: loadedModel,
|
||||
image_type: imageType,
|
||||
max_in_article_images: config.max_in_article_images || 4,
|
||||
image_format: config.image_format || 'webp',
|
||||
});
|
||||
|
||||
console.log('[loadImageSettings] Final settings - Provider:', provider, 'Model:', loadedModel, 'Quality:', quality, 'ImageType:', imageType);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error loading image settings:', error);
|
||||
@@ -456,22 +508,28 @@ export default function SiteSettings() {
|
||||
try {
|
||||
setImageSettingsSaving(true);
|
||||
const config = getCurrentImageConfig();
|
||||
const landscapeSize = getLandscapeSizeForModel(imageSettings.model);
|
||||
const configToSave = {
|
||||
enabled: imageSettings.enabled,
|
||||
service: config.service,
|
||||
provider: config.service,
|
||||
model: config.model,
|
||||
runwareModel: config.service === 'runware' ? config.model : undefined,
|
||||
service: imageProvider,
|
||||
provider: imageProvider,
|
||||
model: imageSettings.model,
|
||||
runwareModel: imageProvider === 'runware' ? imageSettings.model : undefined,
|
||||
image_type: imageSettings.image_type,
|
||||
max_in_article_images: imageSettings.max_in_article_images,
|
||||
image_format: imageSettings.image_format,
|
||||
featured_image_size: imageSettings.featured_image_size,
|
||||
featured_image_size: landscapeSize, // Auto-determined by model
|
||||
};
|
||||
|
||||
await fetchAPI('/v1/system/settings/integrations/image_generation/save/', {
|
||||
console.log('[saveImageSettings] Saving config:', configToSave);
|
||||
|
||||
// URL pattern is /v1/system/settings/integrations/<pk>/save/
|
||||
const result = await fetchAPI('/v1/system/settings/integrations/image_generation/save/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(configToSave),
|
||||
});
|
||||
|
||||
console.log('[saveImageSettings] Save result:', result);
|
||||
toast.success('Image settings saved successfully');
|
||||
} catch (error: any) {
|
||||
console.error('Error saving image settings:', error);
|
||||
@@ -982,55 +1040,111 @@ export default function SiteSettings() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Row 1: Image Quality & Style */}
|
||||
{/* Provider Info Banner - Show which provider is configured */}
|
||||
<div className={`p-3 rounded-lg flex items-center gap-3 ${
|
||||
imageProvider === 'openai'
|
||||
? 'bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800'
|
||||
: 'bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800'
|
||||
}`}>
|
||||
<div className={`p-2 rounded-lg ${
|
||||
imageProvider === 'openai'
|
||||
? 'bg-green-100 dark:bg-green-800'
|
||||
: 'bg-purple-100 dark:bg-purple-800'
|
||||
}`}>
|
||||
<ImageIcon className={`w-4 h-4 ${
|
||||
imageProvider === 'openai'
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
: 'text-purple-600 dark:text-purple-400'
|
||||
}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className={`text-sm font-medium ${
|
||||
imageProvider === 'openai'
|
||||
? 'text-green-700 dark:text-green-300'
|
||||
: 'text-purple-700 dark:text-purple-300'
|
||||
}`}>
|
||||
{imageProvider === 'openai' ? 'OpenAI DALL-E' : 'Runware'} - Image Provider
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{imageProvider === 'openai'
|
||||
? 'Using DALL-E 3 for high-quality AI images'
|
||||
: 'Choose from multiple AI models with different quality tiers'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 1: Image Model/Quality & Style */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label className="mb-2">Image Quality</Label>
|
||||
<SelectDropdown
|
||||
options={[
|
||||
{ value: 'standard', label: 'Standard - Fast & economical (DALL·E 2)' },
|
||||
{ value: 'premium', label: 'Premium - High quality (DALL·E 3)' },
|
||||
{ value: 'best', label: 'Best - Highest quality (Runware)' },
|
||||
]}
|
||||
value={imageQuality}
|
||||
onChange={(value) => setImageQuality(value as 'standard' | 'premium' | 'best')}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Higher quality produces better images</p>
|
||||
<Label className="mb-2">{imageProvider === 'openai' ? 'Image Model' : 'Image Quality'}</Label>
|
||||
{imageProvider === 'openai' ? (
|
||||
<>
|
||||
<SelectDropdown
|
||||
options={DALLE_MODEL_CHOICES.map(m => ({
|
||||
value: m.value,
|
||||
label: m.label,
|
||||
}))}
|
||||
value={imageSettings.model}
|
||||
onChange={(value) => setImageSettings({ ...imageSettings, model: value })}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">DALL-E 3 generates high-quality images (40 credits/image)</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SelectDropdown
|
||||
options={RUNWARE_MODEL_CHOICES.map(m => ({
|
||||
value: m.value === 'runware:97@1' ? 'basic' : m.value === 'bria:10@1' ? 'quality' : 'premium',
|
||||
label: m.label,
|
||||
}))}
|
||||
value={imageQuality}
|
||||
onChange={(value) => {
|
||||
setImageQuality(value as 'basic' | 'quality' | 'premium');
|
||||
const model = value === 'basic' ? 'runware:97@1' : value === 'quality' ? 'bria:10@1' : 'google:4@2';
|
||||
setImageSettings({ ...imageSettings, model });
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Higher credit cost = better image quality</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-2">Image Style</Label>
|
||||
<SelectDropdown
|
||||
options={[
|
||||
{ value: 'realistic', label: 'Realistic' },
|
||||
{ value: 'artistic', label: 'Artistic' },
|
||||
{ value: 'cartoon', label: 'Cartoon' },
|
||||
]}
|
||||
options={availableStyles.map(style => ({
|
||||
value: style.value,
|
||||
label: style.label,
|
||||
}))}
|
||||
value={imageSettings.image_type}
|
||||
onChange={(value) => setImageSettings({ ...imageSettings, image_type: value as any })}
|
||||
onChange={(value) => setImageSettings({ ...imageSettings, image_type: value })}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Choose the visual style that matches your brand</p>
|
||||
{/* Show description of selected style */}
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{availableStyles.find(s => s.value === imageSettings.image_type)?.description || 'Choose the visual style that matches your brand'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Featured Image Size */}
|
||||
<div>
|
||||
{/* Row 2: Featured Image Info Card (compact, not full width) */}
|
||||
<div className="max-w-md">
|
||||
<Label className="mb-2">Featured Image</Label>
|
||||
<div className="p-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gradient-to-r from-purple-500 to-brand-500 text-white">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="font-medium">Featured Image Size</div>
|
||||
<div className="text-xs bg-white/20 px-2 py-1 rounded">Landscape (Model-specific)</div>
|
||||
<div className="p-4 rounded-lg bg-gradient-to-r from-brand-500 to-purple-500 text-white">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-white/20 flex items-center justify-center">
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">Always Landscape</div>
|
||||
<div className="text-sm text-white/80">{currentLandscapeSize} pixels</div>
|
||||
</div>
|
||||
</div>
|
||||
<SelectDropdown
|
||||
options={availableImageSizes}
|
||||
value={imageSettings.featured_image_size}
|
||||
onChange={(value) => setImageSettings({ ...imageSettings, featured_image_size: value })}
|
||||
className="w-full [&_.igny8-select-styled]:bg-white/10 [&_.igny8-select-styled]:border-white/20 [&_.igny8-select-styled]:text-white"
|
||||
/>
|
||||
<p className="text-xs text-white/70 mt-2">
|
||||
In-article images alternate: Square (1024×1024) → Landscape → Square → Landscape
|
||||
<p className="text-xs text-white/70 mt-3">
|
||||
Featured image size is automatically set based on your selected model
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1051,7 +1165,7 @@ export default function SiteSettings() {
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Images 1 & 3: Square | Images 2 & 4: Landscape
|
||||
Images 1 & 3: Square (1024×1024) | Images 2 & 4: Landscape
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -326,12 +326,14 @@ export default function Images() {
|
||||
.sort((a, b) => (a.position || 0) - (b.position || 0));
|
||||
|
||||
pendingInArticle.forEach((img, idx) => {
|
||||
// Position is 0-indexed in backend, but labels should be 1-indexed for users
|
||||
const displayPosition = (img.position ?? idx) + 1;
|
||||
queue.push({
|
||||
imageId: img.id || null,
|
||||
index: queueIndex++,
|
||||
label: `In-Article Image ${img.position || idx + 1}`,
|
||||
label: `In-Article Image ${displayPosition}`,
|
||||
type: 'in_article',
|
||||
position: img.position || idx + 1,
|
||||
position: img.position ?? idx,
|
||||
contentTitle: contentImages.content_title || `Content #${contentId}`,
|
||||
prompt: img.prompt || undefined,
|
||||
status: 'pending',
|
||||
|
||||
Reference in New Issue
Block a user