/** * Content Settings Page - 3 Tabs * Tabs: Content Generation, Publishing, Image Settings * Consolidated settings for content creation workflow */ import { useState, useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { SaveIcon, Loader2Icon, ImageIcon, FileTextIcon, PaperPlaneIcon, SettingsIcon } from '../../icons'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import { fetchAPI } from '../../services/api'; import { useToast } from '../../components/ui/toast/ToastContainer'; import SelectDropdown from '../../components/form/SelectDropdown'; import Label from '../../components/form/Label'; import Checkbox from '../../components/form/input/Checkbox'; import TextArea from '../../components/form/input/TextArea'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import { BoxCubeIcon } from '../../icons'; type TabType = 'content' | 'publishing' | 'images'; interface ImageGenerationSettings { enabled: boolean; service: 'openai' | 'runware'; provider: string; model: string; runwareModel?: string; image_type: 'realistic' | 'artistic' | 'cartoon'; max_in_article_images: number; image_format: 'webp' | 'jpg' | 'png'; desktop_enabled: boolean; mobile_enabled: boolean; featured_image_size: string; desktop_image_size: string; } interface PublishingSettings { autoPublishEnabled: boolean; autoSyncEnabled: boolean; } interface ContentGenerationSettings { appendToPrompt: string; defaultTone: string; defaultLength: string; } // AI Model Config from API interface AIModelConfig { model_name: string; display_name: string; model_type: string; provider: string; valid_sizes?: string[]; quality_tier?: string; credits_per_image?: number; } // Map user-friendly quality to internal service/model configuration const QUALITY_TO_CONFIG: Record = { standard: { service: 'openai', model: 'dall-e-2' }, premium: { service: 'openai', model: 'dall-e-3' }, best: { service: 'runware', model: 'runware:97@1' }, }; // Map internal config back to user-friendly quality const getQualityFromConfig = (service?: string, model?: string): 'standard' | 'premium' | 'best' => { if (service === 'runware') return 'best'; if (model === 'dall-e-3') return 'premium'; return 'standard'; }; // Get available image sizes based on provider and model (from API or fallback) const getImageSizes = (provider: string, model: string, imageModels: AIModelConfig[]) => { // First, try to find the model in the fetched models const modelConfig = imageModels.find(m => m.model_name === model || (m.provider === provider && m.model_name.includes(model)) ); // If found and has valid_sizes, use them if (modelConfig?.valid_sizes && modelConfig.valid_sizes.length > 0) { return modelConfig.valid_sizes.map(size => ({ value: size, label: `${size.replace('x', '×')} pixels` })); } // Fallback to hardcoded sizes for backward compatibility if (provider === 'runware') { return [ { value: '1280x832', label: '1280×832 pixels' }, { value: '1024x1024', label: '1024×1024 pixels' }, { value: '512x512', label: '512×512 pixels' }, ]; } 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' }]; }; // Get tab from URL path function getTabFromPath(pathname: string): TabType { if (pathname.includes('/publishing')) return 'publishing'; if (pathname.includes('/images')) return 'images'; return 'content'; } export default function ContentSettingsPage() { const toast = useToast(); const location = useLocation(); const activeTab = getTabFromPath(location.pathname); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); // Image Models from API - for dynamic size options const [imageModels, setImageModels] = useState([]); // Content Generation Settings const [contentSettings, setContentSettings] = useState({ appendToPrompt: '', defaultTone: 'professional', defaultLength: 'medium', }); // Publishing Settings const [publishingSettings, setPublishingSettings] = useState({ autoPublishEnabled: false, autoSyncEnabled: false, }); // Image Quality const [imageQuality, setImageQuality] = useState<'standard' | 'premium' | 'best'>('premium'); // Image Generation Settings const [imageSettings, setImageSettings] = useState({ enabled: true, service: 'openai', provider: 'openai', model: 'dall-e-3', image_type: 'realistic', max_in_article_images: 2, image_format: 'webp', desktop_enabled: true, mobile_enabled: true, featured_image_size: '1024x1024', desktop_image_size: '1024x1024', }); // Get current provider/model from quality setting const getCurrentConfig = useCallback(() => { const config = QUALITY_TO_CONFIG[imageQuality]; return { service: config.service, model: config.model, }; }, [imageQuality]); // Get available sizes for current quality (uses imageModels from API) const availableSizes = getImageSizes( getCurrentConfig().service, getCurrentConfig().model, imageModels ); useEffect(() => { loadSettings(); }, []); // Update image sizes when quality changes or imageModels are loaded useEffect(() => { const config = getCurrentConfig(); const sizes = getImageSizes(config.service, config.model, imageModels); const defaultSize = sizes.length > 0 ? sizes[0].value : '1024x1024'; const validSizes = sizes.map(s => s.value); const needsFeaturedUpdate = !validSizes.includes(imageSettings.featured_image_size); const needsDesktopUpdate = !validSizes.includes(imageSettings.desktop_image_size); if (needsFeaturedUpdate || needsDesktopUpdate) { setImageSettings(prev => ({ ...prev, service: config.service, provider: config.service, model: config.model, featured_image_size: needsFeaturedUpdate ? defaultSize : prev.featured_image_size, desktop_image_size: needsDesktopUpdate ? defaultSize : prev.desktop_image_size, })); } else { setImageSettings(prev => ({ ...prev, service: config.service, provider: config.service, model: config.model, })); } }, [imageQuality, getCurrentConfig, imageModels]); const loadSettings = async () => { try { setLoading(true); // Load available image models from API (for dynamic sizes) try { const modelsResponse = await fetchAPI('/v1/billing/models/?type=image'); if (modelsResponse?.data) { setImageModels(modelsResponse.data); } else if (Array.isArray(modelsResponse)) { setImageModels(modelsResponse); } } catch (err) { console.log('Image models not available, using hardcoded sizes'); } // Load image generation settings const imageData = await fetchAPI('/v1/system/settings/integrations/image_generation/'); if (imageData) { const quality = getQualityFromConfig(imageData.service || imageData.provider, imageData.model); setImageQuality(quality); setImageSettings({ enabled: imageData.enabled !== false, service: imageData.service || imageData.provider || 'openai', provider: imageData.provider || imageData.service || 'openai', model: imageData.model || 'dall-e-3', runwareModel: imageData.runwareModel, image_type: imageData.image_type || 'realistic', max_in_article_images: imageData.max_in_article_images || 2, image_format: imageData.image_format || 'webp', desktop_enabled: imageData.desktop_enabled !== false, mobile_enabled: imageData.mobile_enabled !== false, featured_image_size: imageData.featured_image_size || '1024x1024', desktop_image_size: imageData.desktop_image_size || '1024x1024', }); } // Load content generation settings try { const contentData = await fetchAPI('/v1/system/settings/content/content_generation/'); if (contentData?.config) { setContentSettings({ appendToPrompt: contentData.config.append_to_prompt || '', defaultTone: contentData.config.default_tone || 'professional', defaultLength: contentData.config.default_length || 'medium', }); } } catch (err) { // Settings may not exist yet, use defaults console.log('Content generation settings not found, using defaults'); } // Load publishing settings try { const publishData = await fetchAPI('/v1/system/settings/content/publishing/'); if (publishData?.config) { setPublishingSettings({ autoPublishEnabled: publishData.config.auto_publish_enabled || false, autoSyncEnabled: publishData.config.auto_sync_enabled || false, }); } } catch (err) { // Settings may not exist yet, use defaults console.log('Publishing settings not found, using defaults'); } } catch (error: any) { console.error('Error loading content settings:', error); } finally { setLoading(false); } }; const handleSave = async () => { try { setSaving(true); if (activeTab === 'images') { const config = getCurrentConfig(); const configToSave = { enabled: imageSettings.enabled, service: config.service, provider: config.service, model: config.model, runwareModel: config.service === 'runware' ? config.model : undefined, image_type: imageSettings.image_type, max_in_article_images: imageSettings.max_in_article_images, image_format: imageSettings.image_format, desktop_enabled: imageSettings.desktop_enabled, mobile_enabled: imageSettings.mobile_enabled, featured_image_size: imageSettings.featured_image_size, desktop_image_size: imageSettings.desktop_image_size, }; await fetchAPI('/v1/system/settings/integrations/image_generation/save/', { method: 'POST', body: JSON.stringify(configToSave), }); } // Save content generation settings if (activeTab === 'content') { await fetchAPI('/v1/system/settings/content/content_generation/save/', { method: 'POST', body: JSON.stringify({ config: { append_to_prompt: contentSettings.appendToPrompt, default_tone: contentSettings.defaultTone, default_length: contentSettings.defaultLength, } }), }); } // Save publishing settings if (activeTab === 'publishing') { await fetchAPI('/v1/system/settings/content/publishing/save/', { method: 'POST', body: JSON.stringify({ config: { auto_publish_enabled: publishingSettings.autoPublishEnabled, auto_sync_enabled: publishingSettings.autoSyncEnabled, } }), }); } toast.success('Settings saved successfully'); } catch (error: any) { console.error('Error saving settings:', error); toast.error(`Failed to save settings: ${error.message}`); } finally { setSaving(false); } }; const tabTitles: Record = { content: 'Content Generation', publishing: 'Publishing', images: 'Image Settings', }; return (
, color: 'blue' }} parent="Content Settings" /> {/* Tab Content */}
{/* Content Generation Tab */} {activeTab === 'content' && (

Content Generation

Customize how your articles are written