feat: Add hamburger menu to Podcast Maker header and move Bible to AnalysisPanel

- Add hamburger menu to Header with gradient styling
- Move Help, My Episodes, My Projects, New Episode into dropdown menu
- Move PodcastBiblePanel into AnalysisPanel header as icon button
- Display Bible details in a styled Popover
- Improve overall header UX and mobile responsiveness
This commit is contained in:
ajaysi
2026-04-07 17:45:43 +05:30
parent e59c77b221
commit edf3f32b3c
3 changed files with 266 additions and 87 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Stack, Box, Typography, Divider, Chip, alpha, Button } from "@mui/material"; import { Stack, Box, Typography, Divider, Chip, alpha, Button, IconButton, Popover, TextField, Tooltip } from "@mui/material";
import { Psychology as PsychologyIcon, Person as PersonIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Input as InputIcon, Groups as GroupsIcon, ListAlt as ListAltIcon, Lightbulb as TipsIcon, Article as ArticleIcon } from "@mui/icons-material"; import { Psychology as PsychologyIcon, Person as PersonIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Input as InputIcon, Groups as GroupsIcon, ListAlt as ListAltIcon, Lightbulb as TipsIcon, Article as ArticleIcon, AutoFixHigh as BibleIcon } from "@mui/icons-material";
import { PodcastAnalysis, PodcastEstimate } from "./types"; import { PodcastAnalysis, PodcastEstimate, PodcastBible } from "./types";
import { GlassyCard, glassyCardSx, SecondaryButton } from "./ui"; import { GlassyCard, glassyCardSx, SecondaryButton } from "./ui";
import { Refresh as RefreshIcon } from "@mui/icons-material"; import { Refresh as RefreshIcon } from "@mui/icons-material";
import { aiApiClient } from "../../api/client"; import { aiApiClient } from "../../api/client";
@@ -15,8 +15,10 @@ interface AnalysisPanelProps {
speakers?: number; speakers?: number;
avatarUrl?: string | null; avatarUrl?: string | null;
avatarPrompt?: string | null; avatarPrompt?: string | null;
bible?: PodcastBible | null;
onRegenerate?: () => void; onRegenerate?: () => void;
onUpdateAnalysis?: (updatedAnalysis: PodcastAnalysis) => void; onUpdateAnalysis?: (updatedAnalysis: PodcastAnalysis) => void;
onUpdateBible?: (updatedBible: PodcastBible) => void;
} }
type TabId = 'inputs' | 'audience' | 'outline' | 'details' | 'takeaways' | 'guest'; type TabId = 'inputs' | 'audience' | 'outline' | 'details' | 'takeaways' | 'guest';
@@ -62,17 +64,21 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
speakers, speakers,
avatarUrl, avatarUrl,
avatarPrompt, avatarPrompt,
bible,
onRegenerate, onRegenerate,
onUpdateAnalysis onUpdateAnalysis,
onUpdateBible
}) => { }) => {
const [activeTab, setActiveTab] = useState<TabId>('inputs'); const [activeTab, setActiveTab] = useState<TabId>('inputs');
const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | null>(null); const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | null>(null);
const [avatarLoading, setAvatarLoading] = useState(false); const [avatarLoading, setAvatarLoading] = useState(false);
const [avatarError, setAvatarError] = useState(false); const [avatarError, setAvatarError] = useState(false);
const [bibleAnchorEl, setBibleAnchorEl] = useState<HTMLElement | null>(null);
// Edit states // Edit states
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [editedAnalysis, setEditedAnalysis] = useState<PodcastAnalysis | null>(null); const [editedAnalysis, setEditedAnalysis] = useState<PodcastAnalysis | null>(null);
const [editedBible, setEditedBible] = useState<PodcastBible | null>(null);
const tabs: TabConfig[] = [ const tabs: TabConfig[] = [
{ id: 'inputs', label: 'Your Inputs', icon: <InputIcon /> }, { id: 'inputs', label: 'Your Inputs', icon: <InputIcon /> },
@@ -326,6 +332,29 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
</Stack> </Stack>
<Stack direction="row" spacing={1}> <Stack direction="row" spacing={1}>
{/* Bible Button */}
{bible && (
<Tooltip title="Podcast Bible - Hyper-personalized context">
<IconButton
onClick={(e) => setBibleAnchorEl(e.currentTarget)}
sx={{
bgcolor: bibleAnchorEl ? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)" : "rgba(102, 126, 234, 0.1)",
border: "1px solid",
borderColor: bibleAnchorEl ? "transparent" : "rgba(102, 126, 234, 0.3)",
borderRadius: 2,
p: 1,
transition: "all 0.2s ease",
"&:hover": {
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
borderColor: "transparent",
},
}}
>
<BibleIcon sx={{ color: bibleAnchorEl ? "#fff" : "#667eea", fontSize: 20 }} />
</IconButton>
</Tooltip>
)}
{isEditing ? ( {isEditing ? (
<> <>
<SecondaryButton <SecondaryButton
@@ -442,6 +471,81 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
<GuestTab analysis={currentAnalysis} /> <GuestTab analysis={currentAnalysis} />
)} )}
</Box> </Box>
{/* Bible Popover */}
<Popover
open={Boolean(bibleAnchorEl)}
anchorEl={bibleAnchorEl}
onClose={() => setBibleAnchorEl(null)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
PaperProps={{
sx: {
mt: 1,
maxWidth: 420,
borderRadius: 3,
background: "linear-gradient(135deg, #1e293b 0%, #0f172a 100%)",
border: "1px solid rgba(102, 126, 234, 0.3)",
boxShadow: "0 10px 40px rgba(102, 126, 234, 0.25)",
},
}}
>
<Box sx={{ p: 2.5 }}>
<Stack spacing={2}>
<Stack direction="row" alignItems="center" spacing={1}>
<BibleIcon sx={{ color: "#a78bfa", fontSize: 24 }} />
<Typography variant="h6" sx={{ color: "#fff", fontWeight: 700 }}>
Podcast Bible
</Typography>
<Tooltip title="Hyper-personalized context derived from your onboarding data. This grounds all research and script generation.">
<IconButton size="small" sx={{ ml: 'auto' }}>
<Typography variant="caption" sx={{ color: "#94a3b8" }}></Typography>
</IconButton>
</Tooltip>
</Stack>
{/* Host Persona */}
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: "rgba(99, 102, 241, 0.1)", border: "1px solid rgba(99, 102, 241, 0.2)" }}>
<Typography variant="caption" sx={{ color: "#a78bfa", fontWeight: 600, mb: 0.5, display: "block" }}>
Host Persona
</Typography>
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.8)", fontSize: "0.8rem" }}>
{bible?.host?.name || "Not set"} {bible?.host?.background || "No background"} {bible?.host?.vocal_style || "No style"}
</Typography>
</Box>
{/* Audience DNA */}
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: "rgba(34, 197, 94, 0.1)", border: "1px solid rgba(34, 197, 94, 0.2)" }}>
<Typography variant="caption" sx={{ color: "#22c55e", fontWeight: 600, mb: 0.5, display: "block" }}>
Audience DNA
</Typography>
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.8)", fontSize: "0.8rem" }}>
{bible?.audience?.expertise_level || "General"} {(bible?.audience?.interests || []).slice(0, 3).join(", ") || "Various interests"}
</Typography>
</Box>
{/* Brand DNA */}
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: "rgba(249, 115, 22, 0.1)", border: "1px solid rgba(249, 115, 22, 0.2)" }}>
<Typography variant="caption" sx={{ color: "#f97316", fontWeight: 600, mb: 0.5, display: "block" }}>
Brand DNA
</Typography>
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.8)", fontSize: "0.8rem" }}>
{bible?.brand?.industry || "No industry"} {bible?.brand?.tone || "No tone"} {bible?.brand?.communication_style || "No style"}
</Typography>
</Box>
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.5)", textAlign: "center", fontSize: "0.7rem" }}>
Podcast Bible personalizes all AI generation for your unique voice
</Typography>
</Stack>
</Box>
</Popover>
</Stack> </Stack>
</GlassyCard> </GlassyCard>
); );

View File

@@ -9,7 +9,6 @@ import { RenderQueue } from "./RenderQueue";
import { RecentEpisodesPreview } from "./RecentEpisodesPreview"; import { RecentEpisodesPreview } from "./RecentEpisodesPreview";
import { ProjectList } from "./ProjectList"; import { ProjectList } from "./ProjectList";
import { PreflightBlockDialog } from "./PreflightBlockDialog"; import { PreflightBlockDialog } from "./PreflightBlockDialog";
import { PodcastBiblePanel } from "./PodcastBiblePanel";
import { import {
Header, Header,
ProgressStepper, ProgressStepper,
@@ -199,14 +198,8 @@ const PodcastDashboard: React.FC = () => {
</Alert> </Alert>
)} )}
{/* Podcast Bible */} {/* Podcast Bible - now in AnalysisPanel header */}
{project && bible && (currentStep === 'analysis' || (currentStep === 'research' && !research)) && !showScriptEditor && !showRenderQueue && (
<PodcastBiblePanel
bible={bible}
onUpdate={(updated) => setBible(updated)}
/>
)}
{(workflow.isAnalyzing || workflow.isResearching || workflow.isGeneratingScript) && ( {(workflow.isAnalyzing || workflow.isResearching || workflow.isGeneratingScript) && (
<Stack direction="row" spacing={2} alignItems="center" sx={{ py: 1.5 }}> <Stack direction="row" spacing={2} alignItems="center" sx={{ py: 1.5 }}>
<CircularProgress size={20} sx={{ color: "#667eea" }} /> <CircularProgress size={20} sx={{ color: "#667eea" }} />
@@ -241,8 +234,10 @@ const PodcastDashboard: React.FC = () => {
speakers={project?.speakers} speakers={project?.speakers}
avatarUrl={project?.avatarUrl} avatarUrl={project?.avatarUrl}
avatarPrompt={project?.avatarPrompt} avatarPrompt={project?.avatarPrompt}
bible={bible}
onRegenerate={() => setShowRegenModal(true)} onRegenerate={() => setShowRegenModal(true)}
onUpdateAnalysis={(updated) => projectState.setAnalysis(updated)} onUpdateAnalysis={(updated) => projectState.setAnalysis(updated)}
onUpdateBible={(updated) => setBible(updated)}
/> />
)} )}

View File

@@ -1,13 +1,18 @@
import React from "react"; import React, { useState } from "react";
import { Stack, Typography, Box } from "@mui/material"; import { Stack, Typography, Box, IconButton, Menu, MenuItem, Divider, ListItemIcon, ListItemText } from "@mui/material";
import { import {
Mic as MicIcon, Mic as MicIcon,
Info as InfoIcon, Menu as MenuIcon,
Close as CloseIcon,
AutoAwesome as AutoAwesomeIcon, AutoAwesome as AutoAwesomeIcon,
LibraryMusic as LibraryMusicIcon, LibraryMusic as LibraryMusicIcon,
Folder as FolderIcon,
Help as HelpIcon,
Add as AddIcon,
BarChart as BarChartIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { PrimaryButton, SecondaryButton } from "../ui"; import { PrimaryButton } from "../ui";
import HeaderControls from "../../shared/HeaderControls"; import HeaderControls from "../../shared/HeaderControls";
interface HeaderProps { interface HeaderProps {
@@ -17,6 +22,36 @@ interface HeaderProps {
export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode }) => { export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const isMenuOpen = Boolean(anchorEl);
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleHelp = () => {
handleMenuClose();
window.open("/docs", "_blank");
};
const handleMyEpisodes = () => {
handleMenuClose();
navigate("/asset-library?source_module=podcast_maker&asset_type=audio");
};
const handleMyProjects = () => {
handleMenuClose();
onShowProjects();
};
const handleNewEpisode = () => {
handleMenuClose();
onNewEpisode();
};
return ( return (
<Box <Box
@@ -25,7 +60,7 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
minWidth: 0, minWidth: 0,
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%)", background: "linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%)",
borderRadius: 3, borderRadius: 3,
p: { xs: 2, md: 2.5 }, p: { xs: 1.5, md: 2.5 },
border: "1px solid rgba(102, 126, 234, 0.15)", border: "1px solid rgba(102, 126, 234, 0.15)",
position: "relative", position: "relative",
overflow: "hidden", overflow: "hidden",
@@ -45,13 +80,14 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
justifyContent="space-between" justifyContent="space-between"
alignItems="center" alignItems="center"
flexWrap="wrap" flexWrap="wrap"
gap={2} gap={1}
> >
{/* Logo and Title */}
<Stack direction="row" alignItems="center" gap={1.5}> <Stack direction="row" alignItems="center" gap={1.5}>
<Box <Box
sx={{ sx={{
width: 44, width: { xs: 36, md: 44 },
height: 44, height: { xs: 36, md: 44 },
borderRadius: 2, borderRadius: 2,
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
display: "flex", display: "flex",
@@ -60,7 +96,7 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
boxShadow: "0 4px 12px rgba(102, 126, 234, 0.3)", boxShadow: "0 4px 12px rgba(102, 126, 234, 0.3)",
}} }}
> >
<MicIcon sx={{ color: "#fff", fontSize: 24 }} /> <MicIcon sx={{ color: "#fff", fontSize: { xs: 20, md: 24 } }} />
</Box> </Box>
<Typography <Typography
variant="h5" variant="h5"
@@ -69,87 +105,131 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
WebkitBackgroundClip: "text", WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent", WebkitTextFillColor: "transparent",
fontWeight: 700, fontWeight: 700,
fontSize: { xs: "1.25rem", md: "1.5rem" }, fontSize: { xs: "1.1rem", sm: "1.25rem", md: "1.5rem" },
letterSpacing: "-0.02em", letterSpacing: "-0.02em",
}} }}
> >
ALwrity Podcast Maker ALwrity Podcast Maker
</Typography> </Typography>
</Stack> </Stack>
<Stack
direction="row" {/* Right side - Hamburger Menu + HeaderControls + Create */}
spacing={1} <Stack direction="row" spacing={1} alignItems="center">
alignItems="center" {/* Header Controls (alerts + user) */}
flexWrap="wrap"
useFlexGap
sx={{
justifyContent: { xs: "flex-start", md: "flex-end" },
gap: { xs: 0.5, md: 1 },
minWidth: 0,
}}
>
<HeaderControls colorMode="light" showAlerts={true} showUser={true} /> <HeaderControls colorMode="light" showAlerts={true} showUser={true} />
<SecondaryButton
onClick={() => window.open("/docs", "_blank")} {/* Hamburger Menu Button */}
startIcon={<InfoIcon />} <IconButton
onClick={handleMenuOpen}
sx={{ sx={{
display: { xs: "none", lg: "flex" }, background: isMenuOpen
borderColor: "rgba(102, 126, 234, 0.3) !important", ? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
color: "#667eea !important", : "rgba(102, 126, 234, 0.1)",
border: "1px solid",
borderColor: isMenuOpen ? "transparent" : "rgba(102, 126, 234, 0.3)",
borderRadius: 2,
p: 1,
transition: "all 0.2s ease",
"&:hover": { "&:hover": {
borderColor: "rgba(102, 126, 234, 0.5) !important", background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
background: "rgba(102, 126, 234, 0.1) !important", borderColor: "transparent",
transform: "scale(1.05)",
}, },
}} }}
> >
Help {isMenuOpen ? (
</SecondaryButton> <CloseIcon sx={{ color: "#fff", fontSize: 20 }} />
<SecondaryButton ) : (
onClick={() => navigate("/asset-library?source_module=podcast_maker&asset_type=audio")} <MenuIcon sx={{ color: "#667eea", fontSize: 20 }} />
startIcon={<LibraryMusicIcon />} )}
tooltip="View all podcast episodes in Asset Library" </IconButton>
sx={{
display: { xs: "none", xl: "flex" }, {/* Dropdown Menu */}
borderColor: "rgba(102, 126, 234, 0.3) !important", <Menu
color: "#667eea !important", anchorEl={anchorEl}
"&:hover": { open={isMenuOpen}
borderColor: "rgba(102, 126, 234, 0.5) !important", onClose={handleMenuClose}
background: "rgba(102, 126, 234, 0.1) !important", anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
PaperProps={{
sx: {
mt: 1,
minWidth: 220,
borderRadius: 2,
background: "linear-gradient(135deg, #1e293b 0%, #0f172a 100%)",
border: "1px solid rgba(102, 126, 234, 0.3)",
boxShadow: "0 10px 40px rgba(102, 126, 234, 0.25)",
"& .MuiMenuItem-root": {
color: "rgba(255, 255, 255, 0.85)",
px: 2,
py: 1.5,
transition: "all 0.15s ease",
"&:hover": {
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%)",
color: "#fff",
},
},
"& .MuiListItemIcon-root": {
color: "#a78bfa",
minWidth: 36,
},
"& .MuiDivider-root": {
borderColor: "rgba(102, 126, 234, 0.2)",
my: 0.5,
},
}, },
}} }}
> >
My Episodes <MenuItem onClick={handleNewEpisode}>
</SecondaryButton> <ListItemIcon>
<SecondaryButton <AddIcon fontSize="small" />
onClick={onShowProjects} </ListItemIcon>
startIcon={<MicIcon />} <ListItemText
tooltip="View and resume saved projects" primary="New Episode"
sx={{ primaryTypographyProps={{ fontWeight: 600 }}
flexShrink: 0, />
display: "flex !important", </MenuItem>
borderColor: "rgba(102, 126, 234, 0.3) !important",
color: "#667eea !important", <MenuItem onClick={handleMyProjects}>
"&:hover": { <ListItemIcon>
borderColor: "rgba(102, 126, 234, 0.5) !important", <FolderIcon fontSize="small" />
background: "rgba(102, 126, 234, 0.1) !important", </ListItemIcon>
}, <ListItemText
}} primary="My Projects"
> primaryTypographyProps={{ fontWeight: 500 }}
My Projects />
</SecondaryButton> </MenuItem>
<PrimaryButton
onClick={onNewEpisode} <MenuItem onClick={handleMyEpisodes}>
startIcon={<AutoAwesomeIcon />} <ListItemIcon>
sx={{ <LibraryMusicIcon fontSize="small" />
flexShrink: 0, </ListItemIcon>
display: "flex", <ListItemText
}} primary="My Episodes"
> primaryTypographyProps={{ fontWeight: 500 }}
New Episode />
</PrimaryButton> </MenuItem>
<Divider />
<MenuItem onClick={handleHelp}>
<ListItemIcon>
<HelpIcon fontSize="small" />
</ListItemIcon>
<ListItemText
primary="Help & Docs"
primaryTypographyProps={{ fontWeight: 500 }}
/>
</MenuItem>
</Menu>
</Stack> </Stack>
</Stack> </Stack>
</Box> </Box>
); );
}; };