Added video studio router and endpoints. Added research router and endpoints. Added youtube router and endpoints. Added onboarding utils router and endpoints. Added onboarding utils service. Added onboarding utils models. Added onboarding utils routes. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
@@ -22,6 +22,16 @@ import {
|
||||
import { HelpOutline as HelpOutlineIcon, Close as CloseIcon, VolumeUp } from "@mui/icons-material";
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
// Import language-aware voice mapping (optional - only used in YouTube Creator context)
|
||||
let getVoicesForLanguage: ((language?: string) => any[]) | undefined;
|
||||
try {
|
||||
const youtubeConstants = require('../../components/YouTubeCreator/constants');
|
||||
getVoicesForLanguage = youtubeConstants.getVoicesForLanguage;
|
||||
} catch {
|
||||
// Not in YouTube Creator context - will use fallback English voices
|
||||
getVoicesForLanguage = undefined;
|
||||
}
|
||||
|
||||
export type AudioGenerationSettings = {
|
||||
voiceId: string;
|
||||
speed: number;
|
||||
@@ -45,28 +55,11 @@ interface AudioSettingsModalProps {
|
||||
isGenerating?: boolean;
|
||||
sceneTitle?: string;
|
||||
isRegenerating?: boolean;
|
||||
language?: string; // Language code (e.g., 'en', 'es', 'fr') - used to filter voice options
|
||||
}
|
||||
|
||||
// Voice options from minimax/speech-02-hd with personality descriptions
|
||||
const VOICE_OPTIONS = [
|
||||
{ id: "Wise_Woman", name: "Wise Woman", personality: "Authoritative, trustworthy female voice - perfect for educational content and expert narration" },
|
||||
{ id: "Friendly_Person", name: "Friendly Person", personality: "Warm, approachable voice - great for welcoming introductions and customer-facing content" },
|
||||
{ id: "Inspirational_girl", name: "Inspirational Girl", personality: "Motivational, uplifting female voice - ideal for inspirational and motivational content" },
|
||||
{ id: "Deep_Voice_Man", name: "Deep Voice Man", personality: "Powerful, commanding male voice - excellent for serious topics and authoritative delivery" },
|
||||
{ id: "Calm_Woman", name: "Calm Woman", personality: "Soothing, composed female voice - perfect for meditation, relaxation, or sensitive topics" },
|
||||
{ id: "Casual_Guy", name: "Casual Guy", personality: "Relaxed, conversational male voice - great for vlogs, tutorials, and informal content" },
|
||||
{ id: "Lively_Girl", name: "Lively Girl", personality: "Energetic, enthusiastic female voice - ideal for exciting announcements and upbeat content" },
|
||||
{ id: "Patient_Man", name: "Patient Man", personality: "Gentle, understanding male voice - perfect for explanations and patient guidance" },
|
||||
{ id: "Young_Knight", name: "Young Knight", personality: "Brave, confident male voice - great for adventure, gaming, and heroic narratives" },
|
||||
{ id: "Determined_Man", name: "Determined Man", personality: "Strong, resolute male voice - excellent for motivational speeches and determined delivery" },
|
||||
{ id: "Lovely_Girl", name: "Lovely Girl", personality: "Sweet, charming female voice - ideal for storytelling and gentle narratives" },
|
||||
{ id: "Decent_Boy", name: "Decent Boy", personality: "Honest, sincere male voice - perfect for testimonials and personal stories" },
|
||||
{ id: "Imposing_Manner", name: "Imposing Manner", personality: "Formal, dignified male voice - great for corporate content and official announcements" },
|
||||
{ id: "Elegant_Man", name: "Elegant Man", personality: "Refined, sophisticated male voice - ideal for luxury, premium content" },
|
||||
{ id: "Abbess", name: "Abbess", personality: "Spiritual, serene female voice - perfect for meditation, philosophy, or contemplative content" },
|
||||
{ id: "Sweet_Girl_2", name: "Sweet Girl 2", personality: "Gentle, melodic female voice - excellent for children's content and soft storytelling" },
|
||||
{ id: "Exuberant_Girl", name: "Exuberant Girl", personality: "Joyful, expressive female voice - ideal for celebrations and happy announcements" },
|
||||
];
|
||||
// Import language-aware voice mapping (fallback to English voices if not in YouTube Creator context)
|
||||
// This will be dynamically loaded based on language prop
|
||||
|
||||
const EMOTION_OPTIONS = ["happy", "sad", "angry", "fearful", "disgusted", "surprised", "neutral"];
|
||||
|
||||
@@ -108,12 +101,38 @@ export const AudioSettingsModal: React.FC<AudioSettingsModalProps> = ({
|
||||
isGenerating = false,
|
||||
sceneTitle,
|
||||
isRegenerating = false,
|
||||
language,
|
||||
}) => {
|
||||
const [settings, setSettings] = useState<AudioGenerationSettings>(initialSettings);
|
||||
|
||||
useEffect(() => {
|
||||
setSettings(initialSettings);
|
||||
}, [initialSettings]);
|
||||
// Fallback English voices (used when language-aware mapping is not available)
|
||||
const ENGLISH_VOICES_FALLBACK = [
|
||||
{ id: "Wise_Woman", name: "Wise Woman", personality: "Authoritative, trustworthy female voice - perfect for educational content and expert narration" },
|
||||
{ id: "Friendly_Person", name: "Friendly Person", personality: "Warm, approachable voice - great for welcoming introductions and customer-facing content" },
|
||||
{ id: "Inspirational_girl", name: "Inspirational Girl", personality: "Motivational, uplifting female voice - ideal for inspirational and motivational content" },
|
||||
{ id: "Deep_Voice_Man", name: "Deep Voice Man", personality: "Powerful, commanding male voice - excellent for serious topics and authoritative delivery" },
|
||||
{ id: "Calm_Woman", name: "Calm Woman", personality: "Soothing, composed female voice - perfect for meditation, relaxation, or sensitive topics" },
|
||||
{ id: "Casual_Guy", name: "Casual Guy", personality: "Relaxed, conversational male voice - great for vlogs, tutorials, and informal content" },
|
||||
{ id: "Lively_Girl", name: "Lively Girl", personality: "Energetic, enthusiastic female voice - ideal for exciting announcements and upbeat content" },
|
||||
{ id: "Patient_Man", name: "Patient Man", personality: "Gentle, understanding male voice - perfect for explanations and patient guidance" },
|
||||
{ id: "Young_Knight", name: "Young Knight", personality: "Brave, confident male voice - great for adventure, gaming, and heroic narratives" },
|
||||
{ id: "Determined_Man", name: "Determined Man", personality: "Strong, resolute male voice - excellent for motivational speeches and determined delivery" },
|
||||
{ id: "Lovely_Girl", name: "Lovely Girl", personality: "Sweet, charming female voice - ideal for storytelling and gentle narratives" },
|
||||
{ id: "Decent_Boy", name: "Decent Boy", personality: "Honest, sincere male voice - perfect for testimonials and personal stories" },
|
||||
{ id: "Imposing_Manner", name: "Imposing Manner", personality: "Formal, dignified male voice - great for corporate content and official announcements" },
|
||||
{ id: "Elegant_Man", name: "Elegant Man", personality: "Refined, sophisticated male voice - ideal for luxury, premium content" },
|
||||
{ id: "Abbess", name: "Abbess", personality: "Spiritual, serene female voice - perfect for meditation, philosophy, or contemplative content" },
|
||||
{ id: "Sweet_Girl_2", name: "Sweet Girl 2", personality: "Gentle, melodic female voice - excellent for children's content and soft storytelling" },
|
||||
{ id: "Exuberant_Girl", name: "Exuberant Girl", personality: "Joyful, expressive female voice - ideal for celebrations and happy announcements" },
|
||||
];
|
||||
|
||||
// Get language-specific voices (use language-aware mapping if available, fallback to English)
|
||||
const VOICE_OPTIONS = useMemo(() => {
|
||||
if (getVoicesForLanguage && language) {
|
||||
return getVoicesForLanguage(language);
|
||||
}
|
||||
return ENGLISH_VOICES_FALLBACK;
|
||||
}, [language]);
|
||||
|
||||
const handleApply = () => {
|
||||
onApplySettings(settings);
|
||||
@@ -169,24 +188,33 @@ export const AudioSettingsModal: React.FC<AudioSettingsModalProps> = ({
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}>
|
||||
Voice Selection Guide
|
||||
</Typography>
|
||||
{language && language !== 'en' && (
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, color: '#4ade80' }}>
|
||||
🌍 <strong>Language-specific voices</strong> are shown for {language.toUpperCase()} content. These voices provide native pronunciation and accent.
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
Choose a voice that matches your content's personality and target audience.
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>YouTube/Vlogging</strong>: Casual Guy (default), Friendly Person - conversational and engaging
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>Educational/Tutorials</strong>: Wise Woman, Deep Voice Man - authoritative and trustworthy
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>Motivational</strong>: Inspirational Girl, Determined Man - energetic and inspiring
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>Relaxing/Storytelling</strong>: Calm Woman, Lovely Girl - soothing and gentle
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block' }}>
|
||||
<strong>Default:</strong> Casual Guy - optimized for engaging YouTube narration.
|
||||
</Typography>
|
||||
{(!language || language === 'en') && (
|
||||
<>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>YouTube/Vlogging</strong>: Casual Guy (default), Friendly Person - conversational and engaging
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>Educational/Tutorials</strong>: Wise Woman, Deep Voice Man - authoritative and trustworthy
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>Motivational</strong>: Inspirational Girl, Determined Man - energetic and inspiring
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
• <strong>Relaxing/Storytelling</strong>: Calm Woman, Lovely Girl - soothing and gentle
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block' }}>
|
||||
<strong>Default:</strong> Casual Guy - optimized for engaging YouTube narration.
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
} arrow placement="right">
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
@@ -194,6 +222,11 @@ export const AudioSettingsModal: React.FC<AudioSettingsModalProps> = ({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
{language && language !== 'en' && (
|
||||
<Typography variant="caption" sx={{ opacity: 0.8, mb: 1, display: 'block', color: '#4ade80' }}>
|
||||
🌍 Showing {language.toUpperCase()} language-specific voices for native pronunciation
|
||||
</Typography>
|
||||
)}
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
value={settings.voiceId}
|
||||
|
||||
616
frontend/src/components/shared/ImageGenerationModal.tsx
Normal file
616
frontend/src/components/shared/ImageGenerationModal.tsx
Normal file
@@ -0,0 +1,616 @@
|
||||
/**
|
||||
* Shared Image Generation Modal
|
||||
*
|
||||
* A reusable, configurable image generation settings modal that supports
|
||||
* hyper-personalization for different use cases (YouTube Creator, Podcast Maker, etc.)
|
||||
* while maintaining consistent core functionality.
|
||||
*
|
||||
* Usage:
|
||||
* - YouTube Creator: Pass YOUTUBE_PRESETS, showModelSelection=true, YOUTUBE_THEME
|
||||
* - Podcast Maker: Pass PODCAST_PRESETS, showModelSelection=false, PODCAST_THEME
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Stack,
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
Divider,
|
||||
alpha,
|
||||
Tooltip,
|
||||
IconButton,
|
||||
Paper,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Info as InfoIcon,
|
||||
HelpOutline as HelpOutlineIcon,
|
||||
Close as CloseIcon,
|
||||
Palette as PaletteIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import {
|
||||
ImageGenerationModalProps,
|
||||
ImageGenerationSettings,
|
||||
ImageStyle,
|
||||
RenderingSpeed,
|
||||
AspectRatio,
|
||||
ImageModel,
|
||||
ImagePreset,
|
||||
DEFAULT_THEME,
|
||||
DEFAULT_MODELS,
|
||||
} from './ImageGenerationModal.types';
|
||||
|
||||
export const ImageGenerationModal: React.FC<ImageGenerationModalProps> = ({
|
||||
// Core
|
||||
open,
|
||||
onClose,
|
||||
onGenerate,
|
||||
initialPrompt,
|
||||
isGenerating = false,
|
||||
|
||||
// Context
|
||||
title = 'Generate Image',
|
||||
contextTitle,
|
||||
promptLabel = 'Visual Prompt',
|
||||
promptHelp = 'Describe what you want to see in the generated image. Include scene context, visual elements, mood, and style preferences.',
|
||||
generateButtonLabel = 'Generate Image',
|
||||
|
||||
// Presets
|
||||
presets = [],
|
||||
presetsLabel = 'Quick Presets',
|
||||
presetsHelp = 'Quickly apply a preset look. Each preset adjusts lighting, composition, and style.',
|
||||
|
||||
// Model selection
|
||||
showModelSelection = false,
|
||||
availableModels = DEFAULT_MODELS,
|
||||
defaultModel = 'ideogram-v3-turbo',
|
||||
|
||||
// Default values
|
||||
defaultStyle = 'Realistic',
|
||||
defaultRenderingSpeed = 'Quality',
|
||||
defaultAspectRatio = '16:9',
|
||||
|
||||
// Theming
|
||||
theme = DEFAULT_THEME,
|
||||
|
||||
// Custom recommendations
|
||||
recommendations,
|
||||
}) => {
|
||||
// State
|
||||
const [prompt, setPrompt] = useState(initialPrompt);
|
||||
const [style, setStyle] = useState<ImageStyle>(defaultStyle);
|
||||
const [renderingSpeed, setRenderingSpeed] = useState<RenderingSpeed>(defaultRenderingSpeed);
|
||||
const [aspectRatio, setAspectRatio] = useState<AspectRatio>(defaultAspectRatio);
|
||||
const [model, setModel] = useState<ImageModel>(defaultModel);
|
||||
|
||||
// Update state when initial values change
|
||||
useEffect(() => {
|
||||
setPrompt(initialPrompt);
|
||||
setStyle(defaultStyle);
|
||||
setRenderingSpeed(defaultRenderingSpeed);
|
||||
setAspectRatio(defaultAspectRatio);
|
||||
setModel(defaultModel);
|
||||
}, [initialPrompt, defaultStyle, defaultRenderingSpeed, defaultAspectRatio, defaultModel]);
|
||||
|
||||
const handleGenerate = () => {
|
||||
const settings: ImageGenerationSettings = {
|
||||
prompt,
|
||||
style,
|
||||
renderingSpeed,
|
||||
aspectRatio,
|
||||
};
|
||||
|
||||
if (showModelSelection) {
|
||||
settings.model = model;
|
||||
}
|
||||
|
||||
onGenerate(settings);
|
||||
};
|
||||
|
||||
const applyPreset = (preset: ImagePreset) => {
|
||||
// Combine the preset prompt with current scene prompt context
|
||||
setPrompt((current) => {
|
||||
if (!current || current.trim() === "" || current.trim() === initialPrompt.trim()) {
|
||||
return `${initialPrompt}\n${preset.prompt}`.trim();
|
||||
}
|
||||
return `${current}\n${preset.prompt}`.trim();
|
||||
});
|
||||
setStyle(preset.style);
|
||||
setRenderingSpeed(preset.renderingSpeed);
|
||||
setAspectRatio(preset.aspectRatio);
|
||||
};
|
||||
|
||||
// Common select styles
|
||||
const selectSx = {
|
||||
backgroundColor: alpha("#ffffff", 0.05),
|
||||
color: "white",
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: "rgba(255,255,255,0.2)",
|
||||
},
|
||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: "rgba(255,255,255,0.3)",
|
||||
},
|
||||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.primaryAccent,
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
background: theme.dialogBackground,
|
||||
backdropFilter: "blur(20px)",
|
||||
border: "1px solid rgba(255,255,255,0.1)",
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ color: "white", fontWeight: 600 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
{contextTitle && (
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.6)", mt: 1 }}>
|
||||
Customize image generation for "{contextTitle}"
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
size="small"
|
||||
sx={{ color: "rgba(255,255,255,0.7)" }}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<Stack spacing={3} sx={{ mt: 1 }}>
|
||||
{/* Presets Section */}
|
||||
{presets.length > 0 && (
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<PaletteIcon sx={{ color: "white", fontSize: "1.2rem" }} />
|
||||
<Typography variant="subtitle1" sx={{ color: "white", fontWeight: 600 }}>
|
||||
{presetsLabel}
|
||||
</Typography>
|
||||
<Tooltip title={presetsHelp} arrow>
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Stack direction={{ xs: "column", sm: "row" }} spacing={1.5}>
|
||||
{presets.map((preset) => (
|
||||
<Paper
|
||||
key={preset.key}
|
||||
onClick={() => applyPreset(preset)}
|
||||
sx={{
|
||||
p: 1.5,
|
||||
flex: 1,
|
||||
cursor: "pointer",
|
||||
backgroundColor: alpha("#ffffff", 0.04),
|
||||
border: "1px solid rgba(255,255,255,0.1)",
|
||||
borderRadius: 2,
|
||||
transition: "all 0.2s ease",
|
||||
"&:hover": {
|
||||
borderColor: alpha(theme.primaryAccent, 0.7),
|
||||
boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
|
||||
backgroundColor: alpha(theme.primaryAccent, 0.08),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ color: "white", fontWeight: 700 }}>
|
||||
{preset.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.7)", lineHeight: 1.5, mb: 0.75 }}>
|
||||
{preset.subtitle}
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1} sx={{ color: "rgba(255,255,255,0.6)", fontSize: "0.8rem" }}>
|
||||
<Typography variant="caption">Style: {preset.style}</Typography>
|
||||
<Typography variant="caption">Speed: {preset.renderingSpeed}</Typography>
|
||||
<Typography variant="caption">AR: {preset.aspectRatio}</Typography>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Prompt Section */}
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<Typography variant="subtitle1" sx={{ color: "white", fontWeight: 600 }}>
|
||||
{promptLabel}
|
||||
</Typography>
|
||||
<Tooltip title={promptHelp} arrow>
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={4}
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="Describe the scene, visual elements, mood, and style..."
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
backgroundColor: alpha("#ffffff", 0.05),
|
||||
color: "white",
|
||||
"& fieldset": {
|
||||
borderColor: "rgba(255,255,255,0.2)",
|
||||
},
|
||||
"&:hover fieldset": {
|
||||
borderColor: "rgba(255,255,255,0.3)",
|
||||
},
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: theme.primaryAccent,
|
||||
},
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.5)", mt: 0.5, display: "block" }}>
|
||||
Be specific about visual elements, lighting, and atmosphere.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ borderColor: "rgba(255,255,255,0.1)" }} />
|
||||
|
||||
{/* Style Selection */}
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Typography variant="subtitle1" sx={{ color: "white", fontWeight: 600 }}>
|
||||
Visual Style
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title="Determines the artistic style of the image generation. Auto lets the AI choose, Fiction creates more stylized/artistic results, and Realistic produces photorealistic results."
|
||||
arrow
|
||||
>
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
value={style}
|
||||
onChange={(e) => setStyle(e.target.value as ImageStyle)}
|
||||
sx={selectSx}
|
||||
>
|
||||
<MenuItem value="Auto">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>Auto</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
AI automatically selects the best style
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="Fiction">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>Fiction</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Stylized, artistic appearance
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="Realistic">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>Realistic</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Photorealistic, professional appearance
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{recommendations?.style && (
|
||||
<Paper
|
||||
sx={{
|
||||
mt: 1.5,
|
||||
p: 1.5,
|
||||
backgroundColor: alpha(theme.primaryAccent, 0.1),
|
||||
border: `1px solid ${alpha(theme.primaryAccent, 0.3)}`,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<InfoIcon sx={{ color: theme.primaryAccent, fontSize: "1.2rem", mt: 0.1 }} />
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.9)", fontWeight: 500, mb: 0.5 }}>
|
||||
Style Impact:
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.7)", lineHeight: 1.6 }}>
|
||||
{recommendations.style}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Rendering Speed */}
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Typography variant="subtitle1" sx={{ color: "white", fontWeight: 600 }}>
|
||||
Generation Speed
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title="Controls the balance between generation speed, cost, and quality. Turbo is fastest and cheapest. Quality is slowest but produces the best results."
|
||||
arrow
|
||||
>
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
value={renderingSpeed}
|
||||
onChange={(e) => setRenderingSpeed(e.target.value as RenderingSpeed)}
|
||||
sx={selectSx}
|
||||
>
|
||||
<MenuItem value="Turbo">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>Turbo ⚡</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Fastest (~10-20s) • Cheapest • Good for quick iterations
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="Default">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>Default ⚖️</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Balanced (~30-60s) • Moderate cost • Great for most content
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="Quality">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>Quality ✨</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Slowest (~60-120s) • Highest quality • Perfect for professional content
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{recommendations?.speed && (
|
||||
<Paper
|
||||
sx={{
|
||||
mt: 1.5,
|
||||
p: 1.5,
|
||||
backgroundColor: alpha(theme.secondaryAccent, 0.1),
|
||||
border: `1px solid ${alpha(theme.secondaryAccent, 0.3)}`,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<InfoIcon sx={{ color: theme.secondaryAccent, fontSize: "1.2rem", mt: 0.1 }} />
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.9)", fontWeight: 500, mb: 0.5 }}>
|
||||
Speed vs Quality:
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.7)", lineHeight: 1.6 }}>
|
||||
{recommendations.speed}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* AI Model Selection (optional) */}
|
||||
{showModelSelection && availableModels.length > 0 && (
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Typography variant="subtitle1" sx={{ color: "white", fontWeight: 600 }}>
|
||||
AI Model
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title="Choose the AI model for image generation. Different models offer different quality levels and costs."
|
||||
arrow
|
||||
>
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
value={model}
|
||||
onChange={(e) => setModel(e.target.value as ImageModel)}
|
||||
sx={selectSx}
|
||||
>
|
||||
{availableModels.map((m) => (
|
||||
<MenuItem key={m.id} value={m.id}>
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>{m.name}</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
{m.description}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{recommendations?.model && (
|
||||
<Paper
|
||||
sx={{
|
||||
mt: 1.5,
|
||||
p: 1.5,
|
||||
backgroundColor: alpha(theme.secondaryAccent, 0.1),
|
||||
border: `1px solid ${alpha(theme.secondaryAccent, 0.3)}`,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<InfoIcon sx={{ color: theme.secondaryAccent, fontSize: "1.2rem", mt: 0.1 }} />
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.9)", fontWeight: 500, mb: 0.5 }}>
|
||||
Model Recommendations:
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.7)", lineHeight: 1.6 }}>
|
||||
{recommendations.model}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Aspect Ratio */}
|
||||
<Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Typography variant="subtitle1" sx={{ color: "white", fontWeight: 600 }}>
|
||||
Aspect Ratio
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title="The width-to-height ratio of the generated image. Choose based on your format: 16:9 for widescreen, 9:16 for vertical/mobile, 1:1 for square."
|
||||
arrow
|
||||
>
|
||||
<IconButton size="small" sx={{ color: "rgba(255,255,255,0.5)" }}>
|
||||
<HelpOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
value={aspectRatio}
|
||||
onChange={(e) => setAspectRatio(e.target.value as AspectRatio)}
|
||||
sx={selectSx}
|
||||
>
|
||||
<MenuItem value="16:9">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>16:9 (Widescreen)</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Standard video format, best for YouTube, web
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="9:16">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>9:16 (Vertical)</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Mobile/social media format (TikTok, Instagram Stories)
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="1:1">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>1:1 (Square)</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Thumbnails, profile images, Instagram posts
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="4:3">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>4:3 (Traditional)</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Classic format, presentations
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value="3:4">
|
||||
<Stack>
|
||||
<Typography sx={{ color: "white" }}>3:4 (Portrait)</Typography>
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.6)" }}>
|
||||
Portrait orientation, mobile apps
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{recommendations?.aspectRatio && (
|
||||
<Paper
|
||||
sx={{
|
||||
mt: 1.5,
|
||||
p: 1.5,
|
||||
backgroundColor: alpha(theme.warningAccent, 0.1),
|
||||
border: `1px solid ${alpha(theme.warningAccent, 0.3)}`,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<InfoIcon sx={{ color: theme.warningAccent, fontSize: "1.2rem", mt: 0.1 }} />
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.9)", fontWeight: 500, mb: 0.5 }}>
|
||||
Format Recommendation:
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.7)", lineHeight: 1.6 }}>
|
||||
{recommendations.aspectRatio}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ p: 3, pt: 2 }}>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
disabled={isGenerating}
|
||||
sx={{ color: "rgba(255,255,255,0.7)" }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleGenerate}
|
||||
disabled={isGenerating || !prompt.trim()}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: isGenerating ? "rgba(255,255,255,0.1)" : theme.primaryAccent,
|
||||
color: "white",
|
||||
"&:hover": {
|
||||
backgroundColor: isGenerating ? "rgba(255,255,255,0.1)" : alpha(theme.primaryAccent, 0.8),
|
||||
},
|
||||
"&:disabled": {
|
||||
backgroundColor: "rgba(255,255,255,0.1)",
|
||||
color: "rgba(255,255,255,0.3)",
|
||||
},
|
||||
px: 3,
|
||||
py: 1,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{isGenerating ? "Generating..." : generateButtonLabel}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
// Re-export types and presets for convenience
|
||||
export * from './ImageGenerationModal.types';
|
||||
export * from './ImageGenerationPresets';
|
||||
|
||||
127
frontend/src/components/shared/ImageGenerationModal.types.ts
Normal file
127
frontend/src/components/shared/ImageGenerationModal.types.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Shared Image Generation Modal Types
|
||||
*
|
||||
* These types enable hyper-personalization for different use cases
|
||||
* (YouTube Creator, Podcast Maker, etc.) while maintaining a consistent API.
|
||||
*/
|
||||
|
||||
// Core image generation settings that get passed to the backend
|
||||
export interface ImageGenerationSettings {
|
||||
prompt: string;
|
||||
style: ImageStyle;
|
||||
renderingSpeed: RenderingSpeed;
|
||||
aspectRatio: AspectRatio;
|
||||
model?: ImageModel;
|
||||
}
|
||||
|
||||
// Style options for image generation
|
||||
export type ImageStyle = 'Auto' | 'Fiction' | 'Realistic';
|
||||
|
||||
// Rendering speed/quality options
|
||||
export type RenderingSpeed = 'Turbo' | 'Default' | 'Quality';
|
||||
|
||||
// Aspect ratio options
|
||||
export type AspectRatio = '1:1' | '16:9' | '9:16' | '4:3' | '3:4';
|
||||
|
||||
// Available AI models for image generation
|
||||
export type ImageModel = 'ideogram-v3-turbo' | 'qwen-image';
|
||||
|
||||
// Preset configuration for quick-apply presets
|
||||
export interface ImagePreset {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
prompt: string;
|
||||
style: ImageStyle;
|
||||
renderingSpeed: RenderingSpeed;
|
||||
aspectRatio: AspectRatio;
|
||||
}
|
||||
|
||||
// Model option for the model selector
|
||||
export interface ModelOption {
|
||||
id: ImageModel;
|
||||
name: string;
|
||||
description: string;
|
||||
costPerImage: string;
|
||||
}
|
||||
|
||||
// Theme configuration for branding
|
||||
export interface ImageModalTheme {
|
||||
// Background colors
|
||||
dialogBackground: string;
|
||||
// Accent colors for info panels
|
||||
primaryAccent: string;
|
||||
secondaryAccent: string;
|
||||
warningAccent: string;
|
||||
}
|
||||
|
||||
// Custom recommendation text for context-specific help
|
||||
export interface CustomRecommendations {
|
||||
style?: React.ReactNode;
|
||||
speed?: React.ReactNode;
|
||||
aspectRatio?: React.ReactNode;
|
||||
model?: React.ReactNode;
|
||||
}
|
||||
|
||||
// Main modal props with hyper-personalization options
|
||||
export interface ImageGenerationModalProps {
|
||||
// Core functionality
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onGenerate: (settings: ImageGenerationSettings) => void;
|
||||
initialPrompt: string;
|
||||
isGenerating?: boolean;
|
||||
|
||||
// Context
|
||||
title?: string;
|
||||
contextTitle?: string; // e.g., scene title, section name
|
||||
promptLabel?: string;
|
||||
promptHelp?: string;
|
||||
generateButtonLabel?: string;
|
||||
|
||||
// Hyper-personalization
|
||||
presets?: ImagePreset[];
|
||||
presetsLabel?: string;
|
||||
presetsHelp?: string;
|
||||
|
||||
// Model selection
|
||||
showModelSelection?: boolean;
|
||||
availableModels?: ModelOption[];
|
||||
defaultModel?: ImageModel;
|
||||
|
||||
// Default values
|
||||
defaultStyle?: ImageStyle;
|
||||
defaultRenderingSpeed?: RenderingSpeed;
|
||||
defaultAspectRatio?: AspectRatio;
|
||||
|
||||
// Theming
|
||||
theme?: ImageModalTheme;
|
||||
|
||||
// Custom recommendations for info panels
|
||||
recommendations?: CustomRecommendations;
|
||||
}
|
||||
|
||||
// Default theme (neutral dark theme)
|
||||
export const DEFAULT_THEME: ImageModalTheme = {
|
||||
dialogBackground: 'rgba(15, 23, 42, 0.95)',
|
||||
primaryAccent: '#667eea',
|
||||
secondaryAccent: '#10b981',
|
||||
warningAccent: '#f59e0b',
|
||||
};
|
||||
|
||||
// Default models available
|
||||
export const DEFAULT_MODELS: ModelOption[] = [
|
||||
{
|
||||
id: 'ideogram-v3-turbo',
|
||||
name: 'Ideogram V3 Turbo ✨',
|
||||
description: 'Photorealistic • Superior text rendering • $0.10/image',
|
||||
costPerImage: '$0.10',
|
||||
},
|
||||
{
|
||||
id: 'qwen-image',
|
||||
name: 'Qwen Image ⚡',
|
||||
description: 'Fast generation • High quality • $0.05/image',
|
||||
costPerImage: '$0.05',
|
||||
},
|
||||
];
|
||||
|
||||
147
frontend/src/components/shared/ImageGenerationPresets.tsx
Normal file
147
frontend/src/components/shared/ImageGenerationPresets.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Preset Configurations for Image Generation Modal
|
||||
*
|
||||
* Each use case (YouTube, Podcast, etc.) has its own presets
|
||||
* that are optimized for that specific content type.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ImagePreset, ImageModalTheme, CustomRecommendations } from './ImageGenerationModal.types';
|
||||
|
||||
// ============================================
|
||||
// YouTube Creator Presets
|
||||
// ============================================
|
||||
|
||||
export const YOUTUBE_PRESETS: ImagePreset[] = [
|
||||
{
|
||||
key: 'engagingHost',
|
||||
title: 'Engaging Host',
|
||||
subtitle: 'Dynamic presenter in engaging video environment',
|
||||
prompt: 'Professional video host in modern studio, dynamic lighting, engaging facial expression, high energy atmosphere, camera-ready appearance, confident posture, vibrant background elements',
|
||||
style: 'Realistic',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
{
|
||||
key: 'cinematicScene',
|
||||
title: 'Cinematic Scene',
|
||||
subtitle: 'Dramatic, movie-like atmosphere with cinematic lighting',
|
||||
prompt: 'Cinematic video scene, dramatic lighting, professional cinematography, engaging narrative atmosphere, high production value, cinematic depth of field, compelling visual storytelling',
|
||||
style: 'Realistic',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
{
|
||||
key: 'professionalPresenter',
|
||||
title: 'Professional Presenter',
|
||||
subtitle: 'Corporate-style presentation with clean, polished look',
|
||||
prompt: 'Professional corporate presenter, clean business attire, polished appearance, neutral background, professional lighting, trustworthy demeanor, business presentation setting',
|
||||
style: 'Realistic',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
{
|
||||
key: 'casualCreator',
|
||||
title: 'Casual Creator',
|
||||
subtitle: 'Relaxed, approachable creator for vlogs and tutorials',
|
||||
prompt: 'Casual content creator, friendly and approachable, comfortable setting, natural lighting, relaxed posture, authentic personality, everyday environment, genuine smile',
|
||||
style: 'Realistic',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
];
|
||||
|
||||
export const YOUTUBE_THEME: ImageModalTheme = {
|
||||
dialogBackground: 'rgba(26, 26, 46, 0.95)',
|
||||
primaryAccent: '#667eea',
|
||||
secondaryAccent: '#10b981',
|
||||
warningAccent: '#f59e0b',
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Podcast Maker Presets
|
||||
// ============================================
|
||||
|
||||
export const PODCAST_PRESETS: ImagePreset[] = [
|
||||
{
|
||||
key: 'studioNeutral',
|
||||
title: 'Studio Neutral',
|
||||
subtitle: 'Clean, well-lit studio, neutral background',
|
||||
prompt: 'Professional podcast studio, neutral light grey backdrop, soft key + fill lighting, subtle depth of field, clear microphone framing',
|
||||
style: 'Realistic',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
{
|
||||
key: 'warmBroadcast',
|
||||
title: 'Warm Broadcast',
|
||||
subtitle: 'Warm tones, friendly and inviting broadcast desk',
|
||||
prompt: 'Warm broadcast desk, soft amber lighting, cozy ambience, gentle vignette, inviting expression, polished but approachable look',
|
||||
style: 'Realistic',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
{
|
||||
key: 'techModern',
|
||||
title: 'Tech Modern',
|
||||
subtitle: 'Crisp, modern look with cool accent lighting',
|
||||
prompt: 'Modern tech podcast set, cool accent lights (teal/purple), minimal backdrop, crisp highlights, premium camera look, subtle bokeh',
|
||||
style: 'Auto',
|
||||
renderingSpeed: 'Quality',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
];
|
||||
|
||||
export const PODCAST_THEME: ImageModalTheme = {
|
||||
dialogBackground: 'rgba(15, 23, 42, 0.95)',
|
||||
primaryAccent: '#667eea',
|
||||
secondaryAccent: '#10b981',
|
||||
warningAccent: '#f59e0b',
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// YouTube-specific Recommendations
|
||||
// ============================================
|
||||
|
||||
export const YOUTUBE_RECOMMENDATIONS: CustomRecommendations = {
|
||||
style: <>
|
||||
<strong>Auto:</strong> Best for most YouTube content, balances professionalism and engagement<br />
|
||||
<strong>Fiction:</strong> Great for creative content, gaming, or stylized presentations<br />
|
||||
<strong>Realistic:</strong> Ideal for educational, corporate, or professional YouTube channels
|
||||
</>,
|
||||
speed: <>
|
||||
<strong>Turbo:</strong> Use for testing and quick iterations (~$0.02/image)<br />
|
||||
<strong>Default:</strong> Best balance for regular YouTube production (~$0.04/image)<br />
|
||||
<strong>Quality:</strong> Use for high-stakes, professional content (~$0.08/image)
|
||||
</>,
|
||||
aspectRatio: <>
|
||||
<strong>16:9:</strong> Standard videos (recommended for most content)<br />
|
||||
<strong>9:16:</strong> YouTube Shorts and mobile-optimized content<br />
|
||||
<strong>1:1:</strong> Thumbnails and square-format promotional content
|
||||
</>,
|
||||
model: <>
|
||||
<strong>Ideogram V3 Turbo:</strong> Best for professional YouTube content with text, logos, or detailed scenes<br />
|
||||
<strong>Qwen Image:</strong> Great for fast iterations and general content creation
|
||||
</>,
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Podcast-specific Recommendations
|
||||
// ============================================
|
||||
|
||||
export const PODCAST_RECOMMENDATIONS: CustomRecommendations = {
|
||||
style: <>
|
||||
<strong>Auto:</strong> Best for most cases, balances realism and style<br />
|
||||
<strong>Fiction:</strong> Great for creative, artistic podcasts with stylized visuals<br />
|
||||
<strong>Realistic:</strong> Ideal for professional, corporate, or news-style podcasts
|
||||
</>,
|
||||
speed: <>
|
||||
<strong>Turbo:</strong> Use for quick iterations and testing (~$0.02/image)<br />
|
||||
<strong>Default:</strong> Best balance for most production use (~$0.04/image)<br />
|
||||
<strong>Quality:</strong> Use for final, high-quality outputs (~$0.08/image)
|
||||
</>,
|
||||
aspectRatio: <>
|
||||
<strong>16:9</strong> is recommended for most podcast videos as it matches standard video player dimensions and provides optimal viewing experience.
|
||||
</>,
|
||||
};
|
||||
|
||||
@@ -22,4 +22,9 @@ export type { AssetLibraryImageModalProps } from './AssetLibraryImageModal';
|
||||
|
||||
// Audio Settings modal (shared across tools)
|
||||
export { AudioSettingsModal } from './AudioSettingsModal';
|
||||
export type { AudioGenerationSettings } from './AudioSettingsModal';
|
||||
export type { AudioGenerationSettings } from './AudioSettingsModal';
|
||||
|
||||
// Image Generation modal (shared across tools)
|
||||
export { ImageGenerationModal } from './ImageGenerationModal';
|
||||
export * from './ImageGenerationModal.types';
|
||||
export * from './ImageGenerationPresets';
|
||||
Reference in New Issue
Block a user