273 lines
10 KiB
TypeScript
273 lines
10 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Accordion,
|
|
AccordionSummary,
|
|
AccordionDetails,
|
|
Grid,
|
|
TextField,
|
|
MenuItem,
|
|
FormControlLabel,
|
|
Checkbox,
|
|
Slider,
|
|
Chip,
|
|
} from '@mui/material';
|
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
import { SectionProps } from './types';
|
|
import { textFieldStyles, accordionStyles } from './styles';
|
|
import { IMAGE_PROVIDERS, AUDIO_PROVIDERS, COMMON_IMAGE_SIZES } from './constants';
|
|
|
|
export const GenerationSettingsSection: React.FC<SectionProps> = ({ state }) => {
|
|
const imageDisabled = !state.enableIllustration;
|
|
const audioDisabled = !state.enableNarration;
|
|
const videoDisabled = !state.enableVideoNarration;
|
|
|
|
const disabledStyles = (disabled: boolean) =>
|
|
disabled
|
|
? {
|
|
opacity: 0.4,
|
|
pointerEvents: 'none',
|
|
}
|
|
: undefined;
|
|
|
|
const renderHeading = (title: string, disabled: boolean) => (
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<Typography variant="subtitle1" sx={{ fontWeight: 600, color: '#1A1611' }}>
|
|
{title}
|
|
</Typography>
|
|
{disabled && <Chip label="Disabled in Story Setup" size="small" />}
|
|
</Box>
|
|
);
|
|
|
|
return (
|
|
<Box sx={{ mt: 4 }}>
|
|
<Typography variant="h6" gutterBottom sx={{ mb: 2, fontWeight: 600 }}>
|
|
Generation Settings
|
|
</Typography>
|
|
<Typography variant="body2" sx={{ mb: 3, color: '#5D4037' }}>
|
|
Configure image, video, and audio generation options for your story.
|
|
</Typography>
|
|
|
|
{/* Image Generation Settings */}
|
|
<Accordion sx={accordionStyles} disabled={imageDisabled}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
{renderHeading('Image Generation Settings', imageDisabled)}
|
|
</AccordionSummary>
|
|
<AccordionDetails sx={disabledStyles(imageDisabled)}>
|
|
<Grid container spacing={3}>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
select
|
|
label="Image Provider"
|
|
value={state.imageProvider || ''}
|
|
onChange={(e) => state.setImageProvider(e.target.value || null)}
|
|
helperText="Select the image generation provider. Leave as 'Auto' to use the default."
|
|
sx={textFieldStyles}
|
|
disabled={imageDisabled}
|
|
>
|
|
{IMAGE_PROVIDERS.map((provider) => (
|
|
<MenuItem key={provider.value} value={provider.value}>
|
|
{provider.label}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
select
|
|
label="Image Size"
|
|
value={`${state.imageWidth}x${state.imageHeight}`}
|
|
onChange={(e) => {
|
|
const [width, height] = e.target.value.split('x').map(Number);
|
|
state.setImageWidth(width);
|
|
state.setImageHeight(height);
|
|
}}
|
|
helperText="Select a common image size or set custom dimensions below."
|
|
sx={textFieldStyles}
|
|
disabled={imageDisabled}
|
|
>
|
|
{COMMON_IMAGE_SIZES.map((size) => (
|
|
<MenuItem key={`${size.width}x${size.height}`} value={`${size.width}x${size.height}`}>
|
|
{size.label}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
type="number"
|
|
label="Image Width"
|
|
value={state.imageWidth}
|
|
onChange={(e) => state.setImageWidth(Number(e.target.value))}
|
|
inputProps={{ min: 256, max: 2048, step: 64 }}
|
|
helperText="Image width in pixels (256-2048)"
|
|
sx={textFieldStyles}
|
|
disabled={imageDisabled}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
type="number"
|
|
label="Image Height"
|
|
value={state.imageHeight}
|
|
onChange={(e) => state.setImageHeight(Number(e.target.value))}
|
|
inputProps={{ min: 256, max: 2048, step: 64 }}
|
|
helperText="Image height in pixels (256-2048)"
|
|
sx={textFieldStyles}
|
|
disabled={imageDisabled}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12}>
|
|
<TextField
|
|
fullWidth
|
|
label="Image Model (Optional)"
|
|
value={state.imageModel || ''}
|
|
onChange={(e) => state.setImageModel(e.target.value || null)}
|
|
placeholder="Leave empty to use default model"
|
|
helperText="Specific model to use for image generation (optional)"
|
|
sx={textFieldStyles}
|
|
disabled={imageDisabled}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
|
|
{/* Video Generation Settings */}
|
|
<Accordion sx={accordionStyles} disabled={videoDisabled}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
{renderHeading('Video Generation Settings', videoDisabled)}
|
|
</AccordionSummary>
|
|
<AccordionDetails sx={disabledStyles(videoDisabled)}>
|
|
<Grid container spacing={3}>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
type="number"
|
|
label="Frames Per Second (FPS)"
|
|
value={state.videoFps}
|
|
onChange={(e) => state.setVideoFps(Number(e.target.value))}
|
|
inputProps={{ min: 15, max: 60, step: 1 }}
|
|
helperText="Video frame rate (15-60 fps). Higher values create smoother video but larger files."
|
|
sx={textFieldStyles}
|
|
disabled={videoDisabled}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<Box>
|
|
<Typography variant="body2" gutterBottom>
|
|
Transition Duration: {state.videoTransitionDuration.toFixed(1)}s
|
|
</Typography>
|
|
<Slider
|
|
value={state.videoTransitionDuration}
|
|
onChange={(_, value) => state.setVideoTransitionDuration(value as number)}
|
|
min={0}
|
|
max={2}
|
|
step={0.1}
|
|
marks={[
|
|
{ value: 0, label: '0s' },
|
|
{ value: 1, label: '1s' },
|
|
{ value: 2, label: '2s' },
|
|
]}
|
|
valueLabelDisplay="auto"
|
|
disabled={videoDisabled}
|
|
/>
|
|
<Typography variant="caption" sx={{ color: '#5D4037' }}>
|
|
Duration of transitions between scenes in seconds
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
</Grid>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
|
|
{/* Audio Generation Settings */}
|
|
<Accordion sx={accordionStyles} disabled={audioDisabled}>
|
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
{renderHeading('Audio Generation Settings', audioDisabled)}
|
|
</AccordionSummary>
|
|
<AccordionDetails sx={disabledStyles(audioDisabled)}>
|
|
<Grid container spacing={3}>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
select
|
|
label="Audio Provider"
|
|
value={state.audioProvider}
|
|
onChange={(e) => state.setAudioProvider(e.target.value)}
|
|
helperText="Text-to-speech provider for narration"
|
|
sx={textFieldStyles}
|
|
disabled={audioDisabled}
|
|
>
|
|
{AUDIO_PROVIDERS.map((provider) => (
|
|
<MenuItem key={provider.value} value={provider.value}>
|
|
{provider.label}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Language Code"
|
|
value={state.audioLang}
|
|
onChange={(e) => state.setAudioLang(e.target.value)}
|
|
placeholder="en"
|
|
helperText="Language code for text-to-speech (e.g., 'en' for English, 'es' for Spanish)"
|
|
sx={textFieldStyles}
|
|
disabled={audioDisabled}
|
|
/>
|
|
</Grid>
|
|
{state.audioProvider === 'gtts' && (
|
|
<Grid item xs={12} md={6}>
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={state.audioSlow}
|
|
onChange={(e) => state.setAudioSlow(e.target.checked)}
|
|
disabled={audioDisabled}
|
|
/>
|
|
}
|
|
label="Slow Speech (gTTS only)"
|
|
/>
|
|
</Grid>
|
|
)}
|
|
{state.audioProvider === 'pyttsx3' && (
|
|
<Grid item xs={12} md={6}>
|
|
<Box>
|
|
<Typography variant="body2" gutterBottom>
|
|
Speech Rate: {state.audioRate} words/min
|
|
</Typography>
|
|
<Slider
|
|
value={state.audioRate}
|
|
onChange={(_, value) => state.setAudioRate(value as number)}
|
|
min={50}
|
|
max={300}
|
|
step={10}
|
|
marks={[
|
|
{ value: 50, label: '50' },
|
|
{ value: 150, label: '150' },
|
|
{ value: 300, label: '300' },
|
|
]}
|
|
valueLabelDisplay="auto"
|
|
disabled={audioDisabled}
|
|
/>
|
|
<Typography variant="caption" sx={{ color: '#5D4037' }}>
|
|
Speech rate in words per minute (pyttsx3 only)
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
)}
|
|
</Grid>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
</Box>
|
|
);
|
|
};
|
|
|