diff --git a/frontend/src/components/PodcastMaker/CreateModal.tsx b/frontend/src/components/PodcastMaker/CreateModal.tsx index 046ae3a4..3c0d077e 100644 --- a/frontend/src/components/PodcastMaker/CreateModal.tsx +++ b/frontend/src/components/PodcastMaker/CreateModal.tsx @@ -15,6 +15,7 @@ import { PodcastConfiguration } from "./CreateStep/PodcastConfiguration"; import { AvatarSelector } from "./CreateStep/AvatarSelector"; import { CreateActions } from "./CreateStep/CreateActions"; import { EnhancedTopicChoicesModal } from "./EnhancedTopicChoicesModal"; +import { TrendingTopicsModal } from "./CreateStep/TrendingTopicsModal"; const ENHANCE_TOPIC_PROGRESS_MESSAGES = [ "Analyzing your topic idea...", @@ -61,6 +62,10 @@ export const CreateModal: React.FC = ({ onCreate, open, defaul const [choicesModalOpen, setChoicesModalOpen] = useState(false); const [editedChoices, setEditedChoices] = useState([]); + // Trending topics state + const [trendingModalOpen, setTrendingModalOpen] = useState(false); + const [trendingLoading, setTrendingLoading] = useState(false); + // Rotate placeholder every 3 seconds useEffect(() => { if (!topicInput) { @@ -582,9 +587,11 @@ export const CreateModal: React.FC = ({ onCreate, open, defaul isUrl={isUrl} showAIDetailsButton={showAIDetailsButton} onAIDetailsClick={handleAIDetailsClick} + onTrendingTopicsClick={() => setTrendingModalOpen(true)} placeholderIndex={placeholderIndex} loading={enhancingTopic} loadingMessage={enhanceTopicMessage} + trendingLoading={trendingLoading} estimatedCost={null} duration={duration} speakers={speakers} @@ -651,6 +658,14 @@ export const CreateModal: React.FC = ({ onCreate, open, defaul onSelectChoice={handleChoiceSelection} loading={enhancingTopic} /> + + {/* Trending Topics Modal */} + setTrendingModalOpen(false)} + onSelectTopic={(topic) => setTopicInput(topic)} + initialKeywords={topicInput} + /> ); diff --git a/frontend/src/components/PodcastMaker/CreateStep/TopicUrlInput.tsx b/frontend/src/components/PodcastMaker/CreateStep/TopicUrlInput.tsx index 546eca6d..08b9fa5f 100644 --- a/frontend/src/components/PodcastMaker/CreateStep/TopicUrlInput.tsx +++ b/frontend/src/components/PodcastMaker/CreateStep/TopicUrlInput.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Box, Typography, TextField, Tooltip, Button, CircularProgress, alpha, Stack, Chip } from "@mui/material"; -import { AutoAwesome as AutoAwesomeIcon, AttachMoney as AttachMoneyIcon } from "@mui/icons-material"; +import { AutoAwesome as AutoAwesomeIcon, AttachMoney as AttachMoneyIcon, TrendingUp as TrendingUpIcon } from "@mui/icons-material"; import { Knobs } from "../types"; export const TOPIC_PLACEHOLDERS = [ @@ -18,9 +18,11 @@ interface TopicUrlInputProps { isUrl: boolean; showAIDetailsButton: boolean; onAIDetailsClick?: () => void; + onTrendingTopicsClick?: () => void; placeholderIndex: number; loading?: boolean; loadingMessage?: string; + trendingLoading?: boolean; estimatedCost?: { ttsCost: number; avatarCost: number; @@ -39,9 +41,11 @@ export const TopicUrlInput: React.FC = ({ isUrl, showAIDetailsButton, onAIDetailsClick, + onTrendingTopicsClick, placeholderIndex, loading = false, loadingMessage, + trendingLoading = false, estimatedCost, duration = 1, speakers = 1, @@ -249,9 +253,47 @@ export const TopicUrlInput: React.FC = ({ /> - {/* Enhance topic with AI button - appears when user types (and not a URL) */} + {/* Enhance topic with AI button + Get Trending Topics - appears when user types (and not a URL) */} {showAIDetailsButton && !isUrl && ( - + + - {loading && ( - - {loadingMessage || "Analyzing your topic and improving clarity..."} - - )} )} + {loading && ( + + {loadingMessage || "Analyzing your topic and improving clarity..."} + + )} ); diff --git a/frontend/src/components/PodcastMaker/CreateStep/TrendingTopicsModal.tsx b/frontend/src/components/PodcastMaker/CreateStep/TrendingTopicsModal.tsx new file mode 100644 index 00000000..52f76324 --- /dev/null +++ b/frontend/src/components/PodcastMaker/CreateStep/TrendingTopicsModal.tsx @@ -0,0 +1,475 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + Tabs, + Tab, + Chip, + Stack, + CircularProgress, + Alert, + LinearProgress, + IconButton, + alpha, +} from "@mui/material"; +import { + TrendingUp as TrendingUpIcon, + Close as CloseIcon, + Public as PublicIcon, + Search as SearchIcon, + AutoAwesome as AutoAwesomeIcon, +} from "@mui/icons-material"; +import { TrendsChart } from "../../Research/steps/components/TrendsChart"; +import { GoogleTrendsData } from "../../Research/types/intent.types"; +import { podcastApi } from "../../../services/podcastApi"; + +interface TrendingTopicsModalProps { + open: boolean; + onClose: () => void; + onSelectTopic: (topic: string) => void; + initialKeywords: string; +} + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +const TabPanel: React.FC = ({ children, value, index }) => ( + +); + +export const TrendingTopicsModal: React.FC = ({ + open, + onClose, + onSelectTopic, + initialKeywords, +}) => { + const [trendsData, setTrendsData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [tabValue, setTabValue] = useState(0); + + const fetchTrends = useCallback(async () => { + if (!initialKeywords.trim()) return; + + const keywords = initialKeywords + .split(/[,;]+/) + .map((k) => k.trim()) + .filter(Boolean) + .slice(0, 5); + + if (keywords.length === 0) return; + + setLoading(true); + setError(null); + setTrendsData(null); + + try { + const result = await podcastApi.getTrendingTopics({ + keywords, + timeframe: "today 12-m", + geo: "US", + }); + + if (result.success && result.data) { + setTrendsData(result.data as GoogleTrendsData); + } else { + setError(result.error || "Failed to fetch trends data"); + } + } catch (err: any) { + setError(err?.response?.data?.detail || err?.message || "Failed to fetch trending topics"); + } finally { + setLoading(false); + } + }, [initialKeywords]); + + useEffect(() => { + if (open && initialKeywords.trim()) { + fetchTrends(); + } + }, [open, initialKeywords, fetchTrends]); + + const handleSelectTopic = (topic: string) => { + onSelectTopic(topic); + onClose(); + }; + + const handleClose = () => { + setTrendsData(null); + setError(null); + setTabValue(0); + onClose(); + }; + + const regions = trendsData?.interest_by_region || []; + const relatedTopics = trendsData?.related_topics || { top: [], rising: [] }; + const relatedQueries = trendsData?.related_queries || { top: [], rising: [] }; + + return ( + + + + + + + + + Trending Topics + + + Google Trends insights for “{initialKeywords}” + + + + + + + + + + {loading && ( + + + + Fetching trending topics from Google Trends... + + + + )} + + {error && ( + + {error} + + )} + + {!loading && trendsData && ( + <> + setTabValue(v)} + variant="scrollable" + scrollButtons="auto" + sx={{ + borderBottom: "1px solid rgba(0,0,0,0.08)", + "& .MuiTab-root": { + textTransform: "none", + fontWeight: 600, + fontSize: "0.875rem", + }, + "& .Mui-selected": { + color: "#667eea", + }, + "& .MuiTabs-indicator": { + background: "linear-gradient(90deg, #667eea 0%, #764ba2 100%)", + }, + }} + > + } iconPosition="start" label="Interest Chart" /> + } iconPosition="start" label="Regions" /> + } iconPosition="start" label="Related Topics" /> + } iconPosition="start" label="Related Queries" /> + + + + + + + + + + {regions.length === 0 ? ( + + + + No regional data available for this topic. + + + ) : ( + + {regions.slice(0, 15).map((region: any, idx: number) => { + const regionName = region.regionName || region.geoName || region.name || `Region ${idx + 1}`; + const value = region.value || region.interest || 0; + const maxVal = Math.max(...regions.slice(0, 15).map((r: any) => r.value || r.interest || 0)); + const pct = maxVal > 0 ? (value / maxVal) * 100 : 0; + return ( + + + {idx + 1} + + + {regionName} + + + + + + + + {value} + + + ); + })} + + )} + + + + {relatedTopics.top.length === 0 && relatedTopics.rising.length === 0 ? ( + + + + No related topics data available. + + + ) : ( + + {relatedTopics.rising.length > 0 && ( + + + Rising Topics + + + {relatedTopics.rising.map((topic: any, idx: number) => { + const label = topic.topic_title || topic.title || topic.query || String(topic); + return ( + handleSelectTopic(label)} + sx={{ + background: "linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(5, 150, 105, 0.12) 100%)", + color: "#059669", + border: "1px solid rgba(16, 185, 129, 0.3)", + fontWeight: 600, + fontSize: "0.75rem", + cursor: "pointer", + mb: 0.5, + "&:hover": { + background: "linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.2) 100%)", + }, + }} + /> + ); + })} + + + )} + {relatedTopics.top.length > 0 && ( + + + Top Topics + + + {relatedTopics.top.map((topic: any, idx: number) => { + const label = topic.topic_title || topic.title || topic.query || String(topic); + return ( + handleSelectTopic(label)} + sx={{ + background: "linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)", + color: "#667eea", + border: "1px solid rgba(102, 126, 234, 0.25)", + fontWeight: 600, + fontSize: "0.75rem", + cursor: "pointer", + mb: 0.5, + "&:hover": { + background: "linear-gradient(135deg, rgba(102, 126, 234, 0.18) 0%, rgba(118, 75, 162, 0.18) 100%)", + }, + }} + /> + ); + })} + + + )} + + )} + + + + {relatedQueries.top.length === 0 && relatedQueries.rising.length === 0 ? ( + + + + No related queries data available. + + + ) : ( + + {relatedQueries.rising.length > 0 && ( + + + Rising Queries + + + {relatedQueries.rising.map((query: any, idx: number) => { + const label = query.query || query.title || String(query); + return ( + handleSelectTopic(label)} + sx={{ + background: "linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.1) 100%)", + color: "#d97706", + border: "1px solid rgba(245, 158, 11, 0.25)", + fontWeight: 600, + fontSize: "0.75rem", + cursor: "pointer", + mb: 0.5, + "&:hover": { + background: "linear-gradient(135deg, rgba(245, 158, 11, 0.18) 0%, rgba(217, 119, 6, 0.18) 100%)", + }, + }} + /> + ); + })} + + + )} + {relatedQueries.top.length > 0 && ( + + + Top Queries + + + {relatedQueries.top.map((query: any, idx: number) => { + const label = query.query || query.title || String(query); + return ( + handleSelectTopic(label)} + sx={{ + background: "linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)", + color: "#667eea", + border: "1px solid rgba(102, 126, 234, 0.25)", + fontWeight: 600, + fontSize: "0.75rem", + cursor: "pointer", + mb: 0.5, + "&:hover": { + background: "linear-gradient(135deg, rgba(102, 126, 234, 0.18) 0%, rgba(118, 75, 162, 0.18) 100%)", + }, + }} + /> + ); + })} + + + )} + + )} + + + )} + + {!loading && !error && !trendsData && ( + + + + Enter a topic and click “Get Trending Topics” to see Google Trends data. + + + )} + + + + + Data from Google Trends + + + + + ); +}; \ No newline at end of file