Production fixes: modal stays open, gradient UI, source links, stepper cleanup

- Fixed progress modals closing prematurely during analysis/research
- Enhanced Create Your Voice Clone button with gradient styling
- Light gradient themes for Expert Quotes, Listener CTAs, Mapped Angles
- Made source reference chips clickable with links in new tab
- Removed duplicate stepper (kept in Header only)
- Skip api-stats endpoint in podcast-only mode
- Combined 3 voice scripts into 1 example
- Added force-include step4_assets router in podcast mode
This commit is contained in:
ajaysi
2026-04-20 06:10:54 +05:30
parent 95edd7d470
commit 34f82c43dd
10 changed files with 231 additions and 131 deletions

View File

@@ -392,30 +392,39 @@ export const CreateActions: React.FC<CreateActionsProps> = ({ reset, submit, can
return () => clearTimeout(timer);
}, []);
// Close modal when analysis completes OR when there's an error
// Close modal only AFTER analysis fully completes (wait for project/analysis to be set)
// Use a ref to track previous isSubmitting to detect the transition from true to false
const prevIsSubmittingRef = useRef(isSubmitting);
const [analysisCompleteRef, setAnalysisCompleteRef] = useState(false);
useEffect(() => {
// Detect transition from submitting to not submitting (analysis complete)
// Track if analysis transitioned from true to false (completed)
const wasSubmitting = prevIsSubmittingRef.current;
const nowNotSubmitting = !isSubmitting;
if (showAnalysisModal && analysisStarted && wasSubmitting && nowNotSubmitting) {
console.warn('[CreateActions] Analysis complete — closing modal and clearing announcement');
// Only close modal if:
// 1. Modal is still shown
// 2. analysisStarted is true
// 3. isSubmitting transitioned from true to false
// 4. AND we're not showing an error
if (showAnalysisModal && analysisStarted && wasSubmitting && nowNotSubmitting && !error) {
// Mark analysis as complete and close after a delay
setAnalysisCompleteRef(true);
console.warn('[CreateActions] Analysis complete — will close modal after delay');
setTimeout(() => {
setShowAnalysisModal(false);
onAnnouncementClear?.();
}, 100);
}, 500);
}
// Update ref for next render
prevIsSubmittingRef.current = isSubmitting;
// If there's an error, also ensure modal is usable
// If there's an error, keep modal open so user can see error message
if (error && showAnalysisModal) {
console.warn('[CreateActions] Error detected:', error);
console.warn('[CreateActions] Error detected — keeping modal open:', error);
}
}, [isSubmitting, showAnalysisModal, analysisStarted, onAnnouncementClear, error]);
}, [isSubmitting, showAnalysisModal, analysisStarted, onAnnouncementClear, error, analysisCompleteRef]);
// Sequential progress - increment every few seconds
useEffect(() => {
@@ -472,7 +481,14 @@ export const CreateActions: React.FC<CreateActionsProps> = ({ reset, submit, can
<Dialog
open={showAnalysisModal}
onClose={() => !isSubmitting && setShowAnalysisModal(false)}
disableEscapeKeyDown={isSubmitting}
onClose={(event, reason) => {
// Only allow closing if NOT submitting and analysis hasn't started
// This prevents modal from closing when user clicks outside while analysis runs
if (!isSubmitting && !analysisStarted) {
setShowAnalysisModal(false);
}
}}
maxWidth="sm"
fullWidth
fullScreen={isMobile}

View File

@@ -12,7 +12,6 @@ import { ProjectList } from "./ProjectList";
import { PreflightBlockDialog } from "./PreflightBlockDialog";
import {
Header,
ProgressStepper,
EstimateCard,
QuerySelection,
ResearchSummary,
@@ -175,55 +174,7 @@ const PodcastDashboard: React.FC = () => {
<Divider sx={{ borderColor: "rgba(0,0,0,0.08)" }} />
{/* Progress Stepper */}
{project && workflow.activeStep >= 0 && (
<ProgressStepper
activeStep={workflow.activeStep}
completedSteps={[
...(analysis ? [0] : []), // Analysis step
...(research ? [1] : []), // Research step
...(scriptData ? [2] : []), // Script step
...(scriptData && renderJobs.length > 0 ? [3] : []), // Render step (if script exists and has jobs)
]}
onStepClick={(stepIndex) => {
// Navigate to the clicked step
// Step indices: 0 = Analysis, 1 = Research, 2 = Script, 3 = Render
if (stepIndex === 0) {
// Navigate to Analysis
setShowScriptEditor(false);
setShowRenderQueue(false);
setCurrentStep('analysis');
} else if (stepIndex === 1) {
// Navigate to Research
if (!analysis) {
workflow.setAnnouncement("Complete Analysis first to access Research.");
return;
}
setShowScriptEditor(false);
setShowRenderQueue(false);
setCurrentStep('research');
} else if (stepIndex === 2) {
// Navigate to Script
if (!research) {
workflow.setAnnouncement("Complete Research first to access Script Editor.");
return;
}
setShowRenderQueue(false);
setShowScriptEditor(true);
setCurrentStep('script');
} else if (stepIndex === 3) {
// Navigate to Render
if (!scriptData) {
workflow.setAnnouncement("Generate and approve script first to access Render Queue.");
return;
}
setShowScriptEditor(false);
setShowRenderQueue(true);
setCurrentStep('render');
}
}}
/>
)}
{/* Progress stepper is in Header - keeping UI clean */}
{/* Resume Alert */}
{workflow.showResumeAlert && project && (

View File

@@ -424,7 +424,13 @@ export const QuerySelection: React.FC<QuerySelectionProps> = ({
{/* Research Progress Modal */}
<Dialog
open={showResearchModal}
onClose={() => !isResearching && setShowResearchModal(false)}
disableEscapeKeyDown={isResearching}
onClose={(event, reason) => {
// Only allow closing if NOT researching and research hasn't started
if (!isResearching && !researchStarted) {
setShowResearchModal(false);
}
}}
maxWidth="sm"
fullWidth
fullScreen={isMobile}

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useCallback } from "react";
import { Stack, Typography, Chip, Divider, Box, alpha, Paper, Stepper, Step, StepLabel, CircularProgress } from "@mui/material";
import { Stack, Typography, Chip, Divider, Box, alpha, Paper, CircularProgress } from "@mui/material";
import {
Insights as InsightsIcon,
Search as SearchIcon,
@@ -7,7 +7,6 @@ import {
Article as ArticleIcon,
AutoAwesome as AutoAwesomeIcon,
ArrowForward as ArrowForwardIcon,
CheckCircle as CheckCircleIcon,
} from "@mui/icons-material";
import { Research, ResearchInsight } from "../types";
import { GlassyCard, glassyCardSx, PrimaryButton } from "../ui";
@@ -55,34 +54,6 @@ export const ResearchSummary: React.FC<ResearchSummaryProps> = ({
return (
<GlassyCard sx={glassyCardSx}>
<Stack spacing={3}>
{/* Step Indicator */}
<Box sx={{ mb: 1 }}>
<Stepper activeStep={1} alternativeLabel>
<Step completed>
<StepLabel
StepIconComponent={() => <CheckCircleIcon sx={{ color: "#22c55e", fontSize: 24 }} />}
>
Analysis
</StepLabel>
</Step>
<Step active>
<StepLabel>
Research
</StepLabel>
</Step>
<Step>
<StepLabel>
Script
</StepLabel>
</Step>
<Step>
<StepLabel>
Render
</StepLabel>
</Step>
</Stepper>
</Box>
<Stack direction="row" justifyContent="space-between" alignItems="center" flexWrap="wrap" gap={2}>
<Stack direction="row" alignItems="center" spacing={2} sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", gap: 1, color: "#0f172a", fontWeight: 700 }}>
@@ -285,27 +256,153 @@ export const ResearchSummary: React.FC<ResearchSummaryProps> = ({
)
)}
{/* Expert Quotes */}
{/* Expert Quotes */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ mb: 1.5, color: "#0f172a", fontWeight: 700 }}>
<Typography variant="h6" sx={{ mb: 1.5, color: "#0f172a", fontWeight: 700, display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{
px: 1.5, py: 0.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #8B5CF6 0%, #EC4899 100%)',
color: '#fff',
fontSize: '0.75rem',
fontWeight: 700
}}>
NEW
</Box>
Expert Quotes
</Typography>
{research.expertQuotes && research.expertQuotes.length > 0 ? (
<Stack spacing={1}>
{research.expertQuotes.slice(0, 4).map((quote, idx) => (
<Paper key={`${quote.source_index}-${idx}`} elevation={0} sx={{ p: 1.5, border: "1px solid rgba(0,0,0,0.06)", borderRadius: 2 }}>
<Typography variant="body2" sx={{ color: "#334155", lineHeight: 1.55 }}>
{quote.quote}
</Typography>
<Typography variant="caption" sx={{ color: "#64748b", display: "block", mt: 0.5 }}>
Source S{quote.source_index}
<Stack spacing={1.5}>
{research.expertQuotes.slice(0, 4).map((quote, idx) => {
const sourceUrl = research.sources?.[quote.source_index - 1]?.url;
return (
<Paper key={`${quote.source_index}-${idx}`} elevation={0} sx={{
p: 2,
borderRadius: 2,
background: 'linear-gradient(135deg, #F5F3FF 0%, #EDE9FE 100%)',
border: '1px solid rgba(139, 92, 246, 0.15)'
}}>
<Typography variant="body2" sx={{ color: "#1E1B4B", lineHeight: 1.65, fontStyle: 'italic', fontWeight: 500 }}>
"{quote.quote}"
</Typography>
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', gap: 0.5 }}>
{sourceUrl ? (
<Chip
label={`Source S${quote.source_index}`}
size="small"
clickable
component="a"
href={sourceUrl}
target="_blank"
rel="noopener noreferrer"
sx={{
background: 'linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%)',
color: '#fff',
fontWeight: 600,
fontSize: '0.7rem',
cursor: 'pointer',
'&:hover': {
background: 'linear-gradient(135deg, #7C3AED 0%, #6D28D9 100%)',
},
}}
/>
) : (
<Chip
label={`Source S${quote.source_index}`}
size="small"
sx={{
background: 'linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%)',
color: '#fff',
fontWeight: 600,
fontSize: '0.7rem',
}}
/>
)}
</Box>
</Paper>
);
})}
</Stack>
) : (
<Typography variant="body2" sx={{ color: "#64748b" }}>
No expert quotes extracted yet.
</Typography>
)}
</Box>
{/* Listener CTAs */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ mb: 1.5, color: "#0f172a", fontWeight: 700, display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{
px: 1.5, py: 0.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #10B981 0%, #14B8A6 100%)',
color: '#fff',
fontSize: '0.75rem',
fontWeight: 700
}}>
NEW
</Box>
Listener CTAs
</Typography>
{research.listenerCta && research.listenerCta.length > 0 ? (
<Stack spacing={1.5}>
{research.listenerCta.slice(0, 4).map((cta, idx) => (
<Paper key={`cta-${idx}`} elevation={0} sx={{
p: 2,
borderRadius: 2,
background: 'linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%)',
border: '1px solid rgba(16, 185, 129, 0.15)'
}}>
<Typography variant="body2" sx={{ color: "#064E3B", lineHeight: 1.65, fontWeight: 500 }}>
{cta}
</Typography>
</Paper>
))}
</Stack>
) : (
<Typography variant="body2" sx={{ color: "#64748b" }}>
No expert quotes extracted yet.
No listener CTAs suggested yet.
</Typography>
)}
</Box>
{/* Mapped Angles */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ mb: 1.5, color: "#0f172a", fontWeight: 700, display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{
px: 1.5, py: 0.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #0EA5E9 0%, #06B6D4 100%)',
color: '#fff',
fontSize: '0.75rem',
fontWeight: 700
}}>
NEW
</Box>
Mapped Angles
</Typography>
{research.mappedAngles && research.mappedAngles.length > 0 ? (
<Stack spacing={1.5}>
{research.mappedAngles.slice(0, 4).map((angle, idx) => (
<Paper key={`angle-${idx}`} elevation={0} sx={{
p: 2,
borderRadius: 2,
background: 'linear-gradient(135deg, #F0F9FF 0%, #E0F2FE 100%)',
border: '1px solid rgba(14, 165, 233, 0.15)'
}}>
<Typography variant="subtitle2" sx={{ color: "#0C4A6E", fontWeight: 700, mb: 0.5 }}>
{angle.title || `Angle ${idx + 1}`}
</Typography>
<Typography variant="body2" sx={{ color: "#075985", lineHeight: 1.65 }}>
{angle.why || "No rationale provided."}
</Typography>
</Paper>
))}
</Stack>
) : (
<Typography variant="body2" sx={{ color: "#64748b" }}>
No mapped angles available yet.
</Typography>
)}
</Box>

View File

@@ -47,6 +47,7 @@ export type Research = {
summary: string;
keyInsights: ResearchInsight[];
factCards: Fact[];
sources?: { title: string; url: string; excerpt?: string }[];
mappedAngles: {
title: string;
why: string;