AI podcast maker performance optimizations

This commit is contained in:
ajaysi
2025-12-12 21:43:09 +05:30
parent 81590cf4db
commit eba5210577
46 changed files with 6176 additions and 1648 deletions

View File

@@ -1,9 +1,11 @@
import React, { useState, useEffect } from "react";
import { Stack, Box, Typography, TextField, Divider, Button, Alert, alpha, Tooltip, Paper, Chip } from "@mui/material";
import React, { useState, useEffect, useMemo } from "react";
import { Stack, Box, Typography, TextField, Divider, Button, Alert, alpha, Tooltip, Paper, Chip, IconButton } from "@mui/material";
import {
AutoAwesome as AutoAwesomeIcon,
Refresh as RefreshIcon,
Info as InfoIcon,
HelpOutline as HelpOutlineIcon,
AttachMoney as AttachMoneyIcon,
} from "@mui/icons-material";
import { CreateProjectPayload, Knobs } from "./types";
import { PrimaryButton, SecondaryButton } from "./ui";
@@ -16,17 +18,25 @@ interface CreateModalProps {
isSubmitting?: boolean;
}
// Rotating placeholder examples for topic ideas
const TOPIC_PLACEHOLDERS = [
"How AI is transforming content marketing in 2024",
"The future of remote work: trends and predictions",
"Sustainable business practices for modern companies",
];
export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaultKnobs, isSubmitting = false }) => {
const { subscription } = useSubscription();
const [idea, setIdea] = useState("");
const [url, setUrl] = useState("");
const [showAIDetailsButton, setShowAIDetailsButton] = useState(false);
const [speakers, setSpeakers] = useState<number>(1);
const [duration, setDuration] = useState<number>(10);
const [duration, setDuration] = useState<number>(1);
const [budgetCap, setBudgetCap] = useState<number>(50);
const [voiceFile, setVoiceFile] = useState<File | null>(null);
const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [knobs, setKnobs] = useState<Knobs>({ ...defaultKnobs });
const [placeholderIndex, setPlaceholderIndex] = useState(0);
// Determine subscription tier restrictions
const tier = subscription?.tier || 'free';
@@ -35,6 +45,16 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
const canUseHD = !isFreeTier && !isBasicTier; // HD only for pro/enterprise
const canUseMultiSpeaker = !isFreeTier; // Multi-speaker for basic+ tiers
// Rotate placeholder every 3 seconds
useEffect(() => {
if (!idea && !url) {
const interval = setInterval(() => {
setPlaceholderIndex((prev) => (prev + 1) % TOPIC_PLACEHOLDERS.length);
}, 3000);
return () => clearInterval(interval);
}
}, [idea, url]);
// Reset HD quality if user downgrades
useEffect(() => {
if (!canUseHD && knobs.bitrate === 'hd') {
@@ -49,11 +69,42 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
}
}, [canUseMultiSpeaker]);
// Ensure duration and speakers are within limits
useEffect(() => {
if (duration > 10) {
setDuration(10);
}
if (speakers > 2) {
setSpeakers(2);
}
}, [duration, speakers]);
// Show AI details button when user starts typing
useEffect(() => {
setShowAIDetailsButton(idea.trim().length > 0);
}, [idea]);
// Calculate estimated cost
const estimatedCost = useMemo(() => {
const chars = Math.max(1000, duration * 900); // ~900 chars per minute
const scenes = Math.ceil((duration * 60) / (knobs.scene_length_target || 45));
const secs = duration * 60;
const ttsCost = (chars / 1000) * 0.05;
const avatarCost = speakers * 0.15;
const videoRate = knobs.bitrate === 'hd' ? 0.06 : 0.03;
const videoCost = secs * videoRate;
const researchCost = 0.3; // Fixed research cost
return {
ttsCost: +ttsCost.toFixed(2),
avatarCost: +avatarCost.toFixed(2),
videoCost: +videoCost.toFixed(2),
researchCost: +researchCost.toFixed(2),
total: +(ttsCost + avatarCost + videoCost + researchCost).toFixed(2),
};
}, [duration, speakers, knobs.bitrate, knobs.scene_length_target]);
const canSubmit = Boolean(idea || url);
const submit = () => {
@@ -72,11 +123,22 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
setIdea("");
setUrl("");
setSpeakers(1);
setDuration(10);
setDuration(1);
setBudgetCap(50);
setVoiceFile(null);
setAvatarFile(null);
setKnobs({ ...defaultKnobs });
setPlaceholderIndex(0);
};
const handleDurationChange = (value: number) => {
const clamped = Math.min(10, Math.max(1, value));
setDuration(clamped);
};
const handleSpeakersChange = (value: number) => {
const clamped = Math.min(2, Math.max(1, value));
setSpeakers(clamped);
};
return (
@@ -84,49 +146,227 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
elevation={0}
sx={{
borderRadius: 3,
border: "1px solid rgba(0,0,0,0.08)",
border: "1px solid rgba(15, 23, 42, 0.08)",
background: "#ffffff",
boxShadow: "0 6px 20px rgba(15, 23, 42, 0.08)",
p: { xs: 3, md: 4 },
boxShadow: "0 1px 3px rgba(15, 23, 42, 0.06), 0 8px 24px rgba(15, 23, 42, 0.08)",
p: { xs: 3, md: 4.5 },
}}
>
<Stack spacing={2}>
<Stack direction="row" spacing={2} alignItems="center" justifyContent="space-between" flexWrap="wrap">
<Stack direction="row" spacing={2} alignItems="center">
<AutoAwesomeIcon sx={{ color: "#667eea" }} />
<Box>
<Typography variant="h5" sx={{ color: "#0f172a", fontWeight: 800 }}>
Create New Podcast Episode
</Typography>
<Typography variant="body2" color="text.secondary">
Provide either a topic idea or a blog post URL. We start AI analysis only after you click Analyze & Continue.
</Typography>
<Stack spacing={3.5}>
{/* Header Section */}
<Stack direction="row" spacing={2} alignItems="flex-start" justifyContent="space-between" flexWrap="wrap" gap={2}>
<Stack direction="row" spacing={2} alignItems="flex-start" sx={{ flex: 1, minWidth: { xs: "100%", md: "60%" } }}>
<Box
sx={{
width: 48,
height: 48,
borderRadius: 2,
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.12) 0%, rgba(118, 75, 162, 0.12) 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}}
>
<AutoAwesomeIcon sx={{ color: "#667eea", fontSize: "1.75rem" }} />
</Box>
<Box sx={{ flex: 1 }}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
<Typography
variant="h5"
sx={{
color: "#0f172a",
fontWeight: 700,
fontSize: { xs: "1.5rem", md: "1.75rem" },
letterSpacing: "-0.02em",
lineHeight: 1.2,
}}
>
Create New Podcast Episode
</Typography>
<Tooltip
title={
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}>
Tips for best results:
</Typography>
<Typography variant="body2" component="div" sx={{ fontSize: "0.875rem" }}>
Provide one clear topic OR a single blog URL (we won't auto-run anything).<br />
• Keep it concise—one sentence topic works best.<br />
• We start analysis only after you confirm, so you stay in control.
</Typography>
</Box>
}
arrow
placement="top"
componentsProps={{
tooltip: {
sx: {
bgcolor: "#0f172a",
color: "#ffffff",
maxWidth: 300,
fontSize: "0.875rem",
p: 1.5,
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
},
},
arrow: {
sx: {
color: "#0f172a",
},
},
}}
>
<IconButton
size="small"
sx={{
color: "#64748b",
"&:hover": {
color: "#667eea",
backgroundColor: alpha("#667eea", 0.08),
}
}}
>
<HelpOutlineIcon fontSize="small" />
</IconButton>
</Tooltip>
</Stack>
</Box>
</Stack>
<Stack direction="row" spacing={1}>
<Chip label={`Plan: ${subscription?.tier || "free"}`} size="small" color="default" />
<Chip label={`Duration: ${duration} min`} size="small" color="default" />
<Chip label={`${speakers} speaker${speakers > 1 ? "s" : ""}`} size="small" color="default" />
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap sx={{ alignItems: "center" }}>
<Tooltip
title={`Your current subscription plan: ${subscription?.tier || "free"}. Upgrade for more features.`}
arrow
placement="top"
>
<Chip
label={`Plan: ${subscription?.tier || "free"}`}
size="small"
sx={{
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.12) 0%, rgba(118, 75, 162, 0.12) 100%)",
color: "#667eea",
fontWeight: 600,
border: "1px solid rgba(102, 126, 234, 0.2)",
fontSize: "0.75rem",
height: 26,
cursor: "help",
}}
/>
</Tooltip>
<Tooltip
title={`Podcast duration: ${duration} minutes. Maximum duration is 10 minutes. Recommended: 5-10 minutes for best results.`}
arrow
placement="top"
>
<Chip
label={`Duration: ${duration} min`}
size="small"
sx={{
background: alpha("#0f172a", 0.06),
color: "#0f172a",
fontWeight: 600,
border: "1px solid rgba(15, 23, 42, 0.12)",
fontSize: "0.75rem",
height: 26,
cursor: "help",
}}
/>
</Tooltip>
<Tooltip
title={`Number of speakers: ${speakers}. Supports 1-2 speakers. Each additional speaker adds avatar generation cost.`}
arrow
placement="top"
>
<Chip
label={`${speakers} speaker${speakers > 1 ? "s" : ""}`}
size="small"
sx={{
background: alpha("#0f172a", 0.06),
color: "#0f172a",
fontWeight: 600,
border: "1px solid rgba(15, 23, 42, 0.12)",
fontSize: "0.75rem",
height: 26,
cursor: "help",
}}
/>
</Tooltip>
<Tooltip
title={
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}>
Estimated Cost Breakdown:
</Typography>
<Typography variant="body2" component="div" sx={{ fontSize: "0.875rem", lineHeight: 1.6 }}>
• Audio Generation: ${estimatedCost.ttsCost}<br />
• Avatar Creation: ${estimatedCost.avatarCost}<br />
• Video Rendering: ${estimatedCost.videoCost}<br />
• Research: ${estimatedCost.researchCost}<br />
<Typography variant="body2" sx={{ fontWeight: 600, mt: 0.5, pt: 0.5, borderTop: "1px solid rgba(255,255,255,0.2)" }}>
Total: ${estimatedCost.total}
</Typography>
<Typography variant="caption" sx={{ fontSize: "0.75rem", opacity: 0.9, mt: 0.5, display: "block" }}>
Based on {duration} min, {speakers} speaker{speakers > 1 ? "s" : ""}, {knobs.bitrate === "hd" ? "HD" : "standard"} quality
</Typography>
</Typography>
</Box>
}
arrow
placement="top"
componentsProps={{
tooltip: {
sx: {
bgcolor: "#0f172a",
color: "#ffffff",
maxWidth: 280,
fontSize: "0.875rem",
p: 1.5,
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
},
},
arrow: {
sx: {
color: "#0f172a",
},
},
}}
>
<Chip
icon={<AttachMoneyIcon sx={{ fontSize: "0.875rem !important" }} />}
label={`Est. $${estimatedCost.total}`}
size="small"
sx={{
background: "linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(5, 150, 105, 0.12) 100%)",
color: "#059669",
fontWeight: 600,
border: "1px solid rgba(16, 185, 129, 0.2)",
fontSize: "0.75rem",
height: 26,
cursor: "help",
}}
/>
</Tooltip>
</Stack>
</Stack>
<Alert severity="info" sx={{ background: "#eef2ff", border: "1px solid #e0e7ff" }}>
<Typography variant="body2" sx={{ color: "#4338ca" }}>
Tips for best results:
</Typography>
<Typography variant="body2" sx={{ color: "#4338ca" }}>
Provide one clear topic OR a single blog URL (we wont auto-run anything).<br />
Keep it conciseone sentence topic works best.<br />
We start analysis only after you confirm, so you stay in control.
</Typography>
</Alert>
<Stack direction={{ xs: "column", md: "row" }} spacing={3} alignItems="stretch">
{/* Topic Idea Section */}
<Box flex={1}>
<Typography variant="subtitle2" sx={{ mb: 1, color: "#0f172a", fontWeight: 700 }}>
Topic Idea
</Typography>
{/* Input Section */}
<Box
sx={{
p: 3,
borderRadius: 2,
background: alpha("#f8fafc", 0.5),
border: "1px solid rgba(15, 23, 42, 0.06)",
}}
>
<Stack direction={{ xs: "column", md: "row" }} spacing={3} alignItems="stretch">
{/* Topic Idea Section */}
<Box flex={1}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
<Typography variant="subtitle2" sx={{ color: "#0f172a", fontWeight: 600, fontSize: "0.9375rem" }}>
Topic Idea
</Typography>
</Stack>
<Tooltip
title="Enter a concise idea. We will expand it into an outline only after you click Analyze."
arrow
@@ -136,7 +376,7 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
fullWidth
multiline
rows={5}
placeholder="e.g., 'How AI is transforming content marketing in 2024'"
placeholder={!idea && !url ? `e.g., "${TOPIC_PLACEHOLDERS[placeholderIndex]}"` : ""}
inputProps={{
sx: {
"&::placeholder": { color: "#94a3b8", opacity: 1 },
@@ -152,25 +392,38 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
}
}}
size="small"
helperText="We will not start analysis until you click Analyze."
helperText="Enter a clear, concise topic. We'll expand it into a full script after you click Analyze."
sx={{
"& .MuiOutlinedInput-root": {
backgroundColor: "#f8fafc",
backgroundColor: "#ffffff",
border: "1.5px solid rgba(15, 23, 42, 0.12)",
borderRadius: 2,
"&:hover": {
backgroundColor: "#f1f5f9",
backgroundColor: "#ffffff",
borderColor: "rgba(102, 126, 234, 0.4)",
},
"&.Mui-focused": {
backgroundColor: "#ffffff",
borderColor: "#667eea",
borderWidth: 2,
},
"& .MuiOutlinedInput-input": {
fontSize: "0.95rem",
lineHeight: 1.5,
fontSize: "0.9375rem",
lineHeight: 1.6,
color: "#0f172a",
fontWeight: 400,
},
},
"& .MuiInputBase-input::placeholder": {
color: "#94a3b8",
opacity: 1,
fontWeight: 400,
},
"& .MuiFormHelperText-root": {
color: "#475569",
color: "#64748b",
fontSize: "0.8125rem",
fontWeight: 400,
mt: 0.75,
},
}}
/>
@@ -189,8 +442,11 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
sx={{
textTransform: "none",
fontSize: "0.875rem",
fontWeight: 600,
borderColor: "#667eea",
borderWidth: 1.5,
color: "#667eea",
borderRadius: 2,
"&:hover": {
borderColor: "#5568d3",
backgroundColor: alpha("#667eea", 0.08),
@@ -203,20 +459,67 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
)}
</Box>
{/* Center OR divider */}
<Stack alignItems="center" justifyContent="center" sx={{ px: { xs: 0, md: 1 } }}>
<Divider orientation="vertical" flexItem sx={{ display: { xs: "none", md: "block" }, borderColor: "rgba(0,0,0,0.08)" }} />
<Divider sx={{ display: { xs: "block", md: "none" }, borderColor: "rgba(0,0,0,0.08)", my: 1 }} />
<Typography variant="caption" sx={{ color: "#64748b", fontWeight: 700, fontSize: "0.75rem" }}>
OR
</Typography>
</Stack>
{/* Center OR divider */}
<Stack alignItems="center" justifyContent="center" sx={{ px: { xs: 0, md: 2 } }}>
<Divider
orientation="vertical"
flexItem
sx={{
display: { xs: "none", md: "block" },
borderColor: "rgba(15, 23, 42, 0.1)",
borderWidth: 1,
}}
/>
<Box
sx={{
display: { xs: "flex", md: "none" },
alignItems: "center",
width: "100%",
my: 2,
}}
>
<Divider sx={{ flex: 1, borderColor: "rgba(15, 23, 42, 0.1)" }} />
<Box
sx={{
px: 2,
py: 0.5,
borderRadius: 2,
background: alpha("#ffffff", 0.8),
border: "1px solid rgba(15, 23, 42, 0.1)",
}}
>
<Typography variant="caption" sx={{ color: "#64748b", fontWeight: 700, fontSize: "0.75rem" }}>
OR
</Typography>
</Box>
<Divider sx={{ flex: 1, borderColor: "rgba(15, 23, 42, 0.1)" }} />
</Box>
<Box
sx={{
display: { xs: "none", md: "flex" },
alignItems: "center",
justifyContent: "center",
width: 40,
height: 40,
borderRadius: "50%",
background: alpha("#ffffff", 0.9),
border: "1px solid rgba(15, 23, 42, 0.1)",
boxShadow: "0 1px 2px rgba(0,0,0,0.05)",
}}
>
<Typography variant="caption" sx={{ color: "#64748b", fontWeight: 700, fontSize: "0.75rem" }}>
OR
</Typography>
</Box>
</Stack>
{/* Blog URL Section */}
<Box flex={1}>
<Typography variant="subtitle2" sx={{ mb: 1, color: "#0f172a", fontWeight: 700 }}>
Blog Post URL
</Typography>
{/* Blog URL Section */}
<Box flex={1}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
<Typography variant="subtitle2" sx={{ color: "#0f172a", fontWeight: 600, fontSize: "0.9375rem" }}>
Blog Post URL
</Typography>
</Stack>
<Tooltip
title="Paste a single article URL. Well fetch insights only after you click Analyze."
arrow
@@ -252,63 +555,246 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
}}
sx={{
"& .MuiOutlinedInput-root": {
backgroundColor: "#f8fafc",
backgroundColor: "#ffffff",
border: "1.5px solid rgba(15, 23, 42, 0.12)",
borderRadius: 2,
"&:hover": {
backgroundColor: "#f1f5f9",
backgroundColor: "#ffffff",
borderColor: "rgba(102, 126, 234, 0.4)",
},
"&.Mui-focused": {
backgroundColor: "#ffffff",
borderColor: "#667eea",
borderWidth: 2,
},
},
"& .MuiInputBase-input": {
color: "#0f172a",
fontSize: "0.9375rem",
fontWeight: 400,
},
"& .MuiInputLabel-root": {
color: "#64748b",
fontSize: "0.9375rem",
"&.Mui-focused": {
color: "#667eea",
},
},
"& .MuiInputBase-input::placeholder": {
color: "#94a3b8",
opacity: 1,
fontWeight: 400,
},
"& .MuiFormHelperText-root": {
color: "#475569",
color: "#64748b",
fontSize: "0.8125rem",
fontWeight: 400,
mt: 0.75,
},
}}
/>
</Tooltip>
</Box>
</Stack>
</Stack>
</Box>
{/* Quick settings for duration and speakers */}
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
<TextField
label="Duration (minutes)"
type="number"
value={duration}
onChange={(e) => setDuration(Math.max(1, Number(e.target.value) || 0))}
InputProps={{ inputProps: { min: 1, max: 60 } }}
size="small"
helperText="Typical podcasts: 5-20 minutes"
sx={{
maxWidth: 220,
"& .MuiOutlinedInput-root": {
backgroundColor: "#f8fafc",
"&:hover": { backgroundColor: "#f1f5f9" },
},
}}
/>
<TextField
label="Number of speakers"
type="number"
value={speakers}
onChange={(e) => setSpeakers(Math.min(4, Math.max(1, Number(e.target.value) || 1)))}
InputProps={{ inputProps: { min: 1, max: 4 } }}
size="small"
helperText="Supports single or panel style"
sx={{
maxWidth: 220,
"& .MuiOutlinedInput-root": {
backgroundColor: "#f8fafc",
"&:hover": { backgroundColor: "#f1f5f9" },
},
}}
/>
</Stack>
{/* Settings Section */}
<Box
sx={{
p: 3,
borderRadius: 2,
background: alpha("#f8fafc", 0.5),
border: "1px solid rgba(15, 23, 42, 0.06)",
}}
>
<Typography variant="subtitle2" sx={{ mb: 2, color: "#0f172a", fontWeight: 600, fontSize: "0.9375rem" }}>
Podcast Settings
</Typography>
<Stack direction={{ xs: "column", sm: "row" }} spacing={2} alignItems="flex-start">
<Stack direction={{ xs: "column", sm: "row" }} spacing={2} sx={{ flex: 1 }}>
<TextField
label="Duration (minutes)"
type="number"
value={duration}
onChange={(e) => handleDurationChange(Number(e.target.value) || 1)}
InputProps={{ inputProps: { min: 1, max: 10 } }}
size="small"
helperText={duration > 10 ? "Maximum duration is 10 minutes" : `Recommended: 1-3 minutes for quick tests (currently: ${duration} min)`}
error={duration > 10}
sx={{
maxWidth: 220,
"& .MuiOutlinedInput-root": {
backgroundColor: "#ffffff",
border: "1.5px solid rgba(15, 23, 42, 0.12)",
borderRadius: 2,
"&:hover": {
backgroundColor: "#ffffff",
borderColor: "rgba(102, 126, 234, 0.4)",
},
"&.Mui-focused": {
borderColor: "#667eea",
borderWidth: 2,
},
},
"& .MuiInputLabel-root": {
color: "#64748b",
"&.Mui-focused": {
color: "#667eea",
},
},
"& .MuiFormHelperText-root": {
color: "#64748b",
fontSize: "0.8125rem",
},
}}
/>
<TextField
label="Number of speakers"
type="number"
value={speakers}
onChange={(e) => handleSpeakersChange(Number(e.target.value) || 1)}
InputProps={{ inputProps: { min: 1, max: 2 } }}
size="small"
helperText={speakers > 2 ? "Maximum 2 speakers supported" : `Supports 1-2 speakers (currently: ${speakers})`}
error={speakers > 2}
sx={{
maxWidth: 220,
"& .MuiOutlinedInput-root": {
backgroundColor: "#ffffff",
border: "1.5px solid rgba(15, 23, 42, 0.12)",
borderRadius: 2,
"&:hover": {
backgroundColor: "#ffffff",
borderColor: "rgba(102, 126, 234, 0.4)",
},
"&.Mui-focused": {
borderColor: "#667eea",
borderWidth: 2,
},
},
"& .MuiInputLabel-root": {
color: "#64748b",
"&.Mui-focused": {
color: "#667eea",
},
},
"& .MuiFormHelperText-root": {
color: "#64748b",
fontSize: "0.8125rem",
},
}}
/>
</Stack>
{/* Cost Breakdown Panel - positioned in empty space */}
<Paper
elevation={0}
sx={{
p: 2.5,
background: "linear-gradient(135deg, rgba(16, 185, 129, 0.08) 0%, rgba(5, 150, 105, 0.08) 100%)",
border: "1.5px solid rgba(16, 185, 129, 0.2)",
borderRadius: 2,
minWidth: { xs: "100%", sm: 300 },
flex: { xs: "none", sm: "0 0 auto" },
boxShadow: "0 2px 8px rgba(16, 185, 129, 0.08)",
}}
>
<Stack spacing={1.5}>
<Stack direction="row" spacing={1} alignItems="center">
<Box
sx={{
width: 32,
height: 32,
borderRadius: 1.5,
background: "linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(5, 150, 105, 0.15) 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<AttachMoneyIcon sx={{ fontSize: "1.125rem", color: "#059669" }} />
</Box>
<Typography variant="subtitle2" sx={{ color: "#0f172a", fontWeight: 600, fontSize: "0.875rem" }}>
Estimated Cost
</Typography>
</Stack>
<Typography
variant="h5"
sx={{
color: "#059669",
fontWeight: 700,
fontSize: "1.75rem",
lineHeight: 1.2,
}}
>
${estimatedCost.total}
</Typography>
<Stack spacing={0.75} sx={{ mt: 0.5 }}>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="caption" sx={{ color: "#64748b", fontSize: "0.8125rem", fontWeight: 400 }}>
Audio Generation
</Typography>
<Typography variant="caption" sx={{ color: "#0f172a", fontSize: "0.8125rem", fontWeight: 600 }}>
${estimatedCost.ttsCost}
</Typography>
</Box>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="caption" sx={{ color: "#64748b", fontSize: "0.8125rem", fontWeight: 400 }}>
Avatar Creation
</Typography>
<Typography variant="caption" sx={{ color: "#0f172a", fontSize: "0.8125rem", fontWeight: 600 }}>
${estimatedCost.avatarCost}
</Typography>
</Box>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="caption" sx={{ color: "#64748b", fontSize: "0.8125rem", fontWeight: 400 }}>
Video Rendering
</Typography>
<Typography variant="caption" sx={{ color: "#0f172a", fontSize: "0.8125rem", fontWeight: 600 }}>
${estimatedCost.videoCost}
</Typography>
</Box>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="caption" sx={{ color: "#64748b", fontSize: "0.8125rem", fontWeight: 400 }}>
Research
</Typography>
<Typography variant="caption" sx={{ color: "#0f172a", fontSize: "0.8125rem", fontWeight: 600 }}>
${estimatedCost.researchCost}
</Typography>
</Box>
</Stack>
<Box
sx={{
mt: 1,
pt: 1.5,
borderTop: "1.5px solid rgba(16, 185, 129, 0.15)",
}}
>
<Typography variant="caption" sx={{ color: "#64748b", fontSize: "0.75rem", fontWeight: 500 }}>
{duration} min {speakers} speaker{speakers > 1 ? "s" : ""} {knobs.bitrate === "hd" ? "HD" : "Standard"} quality
</Typography>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
<Alert severity="info" sx={{ background: "#ecfeff", border: "1px solid #bae6fd", borderRadius: 1 }}>
<Typography variant="body2" sx={{ fontSize: "0.9rem", color: "#0ea5e9" }}>
You can provide either a topic idea or a blog post URL. We wont make any external AI calls until you click Analyze & Continue.
{/* Info Banner */}
<Alert
severity="info"
icon={<InfoIcon sx={{ color: "#6366f1", fontSize: "1.125rem" }} />}
sx={{
background: alpha("#f0f4ff", 0.6),
border: "1px solid rgba(99, 102, 241, 0.15)",
borderRadius: 2,
boxShadow: "0 1px 3px rgba(99, 102, 241, 0.08)",
"& .MuiAlert-message": {
width: "100%",
},
}}
>
<Typography variant="body2" sx={{ fontSize: "0.875rem", color: "#475569", lineHeight: 1.6, fontWeight: 400 }}>
You can provide either a topic idea or a blog post URL. We won't make any external AI calls until you click "Analyze & Continue".
</Typography>
</Alert>