import React, { useState, useCallback, useEffect, useRef } from "react"; import { Box, Typography, TextField, Tooltip, Button, CircularProgress, alpha, Stack, Chip, IconButton, Collapse } from "@mui/material"; import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome"; import AttachMoneyIcon from "@mui/icons-material/AttachMoney"; import TrendingUpIcon from "@mui/icons-material/TrendingUp"; import MicIcon from "@mui/icons-material/Mic"; import StopIcon from "@mui/icons-material/Stop"; import LanguageIcon from "@mui/icons-material/Language"; import NewspaperIcon from "@mui/icons-material/Newspaper"; import ShowChartIcon from "@mui/icons-material/ShowChart"; import SchoolIcon from "@mui/icons-material/School"; import PublicIcon from "@mui/icons-material/Public"; import LightbulbIcon from "@mui/icons-material/Lightbulb"; import { Knobs } from "../types"; import { podcastApi } from "../../../services/podcastApi"; import { WebsitePreviewModal } from "./WebsitePreviewModal"; export const TOPIC_PLACEHOLDERS = [ "Industry insights: Latest trends in AI for Content Marketing", "Product deep-dive: How our new feature solves common pain points", "Educational: 5 ways to improve your workflow with automation", "Thought leadership: The future of decentralized finance (DeFi)", "Interview prep: Key questions for your next tech hiring round", "Podcast prep: Analyzing the impact of remote work on mental health", ]; interface TopicUrlInputProps { value: string; onChange: (value: string) => void; isUrl: boolean; showAIDetailsButton: boolean; onAIDetailsClick?: () => void; onTrendingTopicsClick?: () => void; onCategoryResearchClick?: (category: "news" | "finance" | "research-paper" | "personal-site", websiteUrl?: string) => void; placeholderIndex: number; loading?: boolean; loadingMessage?: string; trendingLoading?: boolean; categoryResearchLoading?: boolean; // Estimated cost - can be a number (from pre-estimate) or object (from analyze response) estimatedCost?: number | { analysisCost: number; researchCost: number; scriptCost: number; ttsCost: number; voiceCloneCost: number; avatarCost: number; videoCost: number; total: number; } | null; duration?: number; speakers?: number; knobs?: Knobs; podcastMode?: string; // Website extraction data - passed from parent for use with AI enhance extractedData?: { title?: string; text?: string; summary?: string; highlights?: string[]; url: string; image?: string; favicon?: string; subpages?: Array<{id?: string; title?: string; url?: string; summary?: string; text?: string}>; } | null; setExtractedData?: (data: any) => void; } interface SpeechRecognitionType { lang: string; continuous: boolean; interimResults: boolean; maxAlternatives: number; onresult: ((event: { results: { isFinal: boolean; [index: number]: { transcript: string } }[], resultIndex: number }) => void) | null; onerror: ((event: { error: string }) => void) | null; onend: (() => void) | null; onstart: (() => void) | null; start: () => void; stop: () => void; abort: () => void; } declare global { interface Window { SpeechRecognition: new () => SpeechRecognitionType; webkitSpeechRecognition: new () => SpeechRecognitionType; } } export const TopicUrlInput: React.FC = ({ value, onChange, isUrl, showAIDetailsButton, onAIDetailsClick, onTrendingTopicsClick, onCategoryResearchClick, placeholderIndex, loading = false, loadingMessage, trendingLoading = false, categoryResearchLoading = false, estimatedCost, duration = 1, speakers = 1, knobs, podcastMode = "audio_video", extractedData: extractedDataProp, setExtractedData: setExtractedDataProp, }) => { // Helper to get total cost from various estimate formats (number | object | null) const getTotalCost = (cost: number | { total: number } | null | undefined): number | null => { if (cost === null || cost === undefined) return null; if (typeof cost === "number") return cost; if (typeof cost === "object" && "total" in cost) return cost.total; return null; }; const totalCost = getTotalCost(estimatedCost); const [isListening, setIsListening] = useState(false); const [error, setError] = useState(null); const recognitionRef = useRef(null); // Use props if provided, otherwise use local state (for backward compatibility) const [localExtractedData, setLocalExtractedData] = useState(null); const _extractedData = extractedDataProp !== undefined ? extractedDataProp : localExtractedData; const _setExtractedData = setExtractedDataProp || setLocalExtractedData; // Website extraction state const [showWebsiteInput, setShowWebsiteInput] = useState(false); const [websiteUrl, setWebsiteUrl] = useState(""); const [isExtracting, setIsExtracting] = useState(false); const [extractedData, setExtractedData] = useState<{title?: string; text?: string; summary?: string; highlights?: string[]; url: string; image?: string; favicon?: string; subpages?: Array<{id?: string; title?: string; url?: string; summary?: string; text?: string}>} | null>(null); const [showPreviewModal, setShowPreviewModal] = useState(false); const [websiteError, setWebsiteError] = useState(null); const isSupported = typeof window !== 'undefined' && (window.SpeechRecognition !== undefined || window.webkitSpeechRecognition !== undefined); const getBrowserLanguage = (): string => { const lang = (navigator.language || '').toLowerCase(); if (lang.startsWith('en')) return 'en-US'; if (lang.startsWith('hi')) return 'hi-IN'; if (lang.startsWith('es')) return 'es-ES'; if (lang.startsWith('fr')) return 'fr-FR'; if (lang.startsWith('de')) return 'de-DE'; if (lang.startsWith('zh')) return 'zh-CN'; if (lang.startsWith('ja')) return 'ja-JP'; if (lang.startsWith('ko')) return 'ko-KR'; return 'en-US'; }; const startListening = useCallback(() => { if (!isSupported) { setError('Speech recognition is not supported in this browser. Try Chrome or Edge.'); return; } setError(null); const SpeechRecognitionAPI = window.SpeechRecognition || (window as any).webkitSpeechRecognition; if (!recognitionRef.current) { const recognition = new SpeechRecognitionAPI() as SpeechRecognitionType; recognition.lang = getBrowserLanguage(); recognition.continuous = false; recognition.interimResults = true; recognition.maxAlternatives = 1; recognition.onresult = (event) => { let transcript = ''; let isFinal = false; for (let i = 0; i < event.results.length; i++) { transcript += event.results[i][0].transcript; if (event.results[i].isFinal) { isFinal = true; } } if (isFinal) { const newValue = value ? `${value} ${transcript.trim()}`.trim() : transcript.trim(); onChange(newValue); } }; recognition.onerror = (event) => { console.error('[Speech] Error:', event.error); if (event.error === 'not-allowed') { setError('Microphone access denied. Please allow microphone access in your browser settings.'); } else if (event.error === 'network') { setError('Network error. Please check your internet connection.'); } else if (event.error !== 'aborted') { setError(`Speech recognition error: ${event.error}`); } setIsListening(false); }; recognition.onend = () => { setIsListening(false); }; recognitionRef.current = recognition; } recognitionRef.current.onstart = () => { setIsListening(true); }; try { recognitionRef.current.start(); } catch (e) { console.error('[Speech] Start error:', e); setError('Failed to start speech recognition. Please try again.'); } }, [isSupported, onChange, value]); const stopListening = useCallback(() => { if (recognitionRef.current) { recognitionRef.current.stop(); } setIsListening(false); }, []); const handleMicClick = useCallback(() => { if (isListening) { stopListening(); } else { startListening(); } }, [isListening, stopListening, startListening]); useEffect(() => { return () => { if (recognitionRef.current) { recognitionRef.current.abort(); } }; }, []); return ( {/* Header with gradient background */} 1 {!showWebsiteInput && ( } label="Your Website" onClick={() => setShowWebsiteInput(true)} disabled={loading} size="small" sx={{ background: "rgba(102, 126, 234, 0.08)", color: "#667eea", border: "1px solid rgba(102, 126, 234, 0.25)", fontWeight: 600, fontSize: "0.75rem", height: 26, "&:hover": { background: "rgba(102, 126, 234, 0.15)", transform: "scale(1.02)", }, }} /> )} Estimated Cost: Total: ${totalCost} Based on {duration} min, {speakers} speaker{speakers > 1 ? "s" : ""}, {podcastMode} mode ) : ( "Estimate unavailable. Pricing data not found." ) } arrow placement="top" > } label={totalCost ? `Est. $${totalCost}` : "Est. Unavailable"} size="small" sx={{ background: totalCost ? "linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(5, 150, 105, 0.12) 100%)" : "rgba(100, 116, 139, 0.12)", color: totalCost ? "#059669" : "#475569", fontWeight: 600, border: totalCost ? "1px solid rgba(16, 185, 129, 0.2)" : "1px solid rgba(100, 116, 139, 0.25)", fontSize: "0.75rem", height: 26, cursor: "help", }} /> {/* Website input row - appears when user clicks "Your Website" chip */} setWebsiteUrl(e.target.value)} disabled={isExtracting} error={!!websiteError} helperText={websiteError} sx={{ "& .MuiOutlinedInput-root": { backgroundColor: "#f8fafc", fontSize: "0.875rem", "&.Mui-focused": { backgroundColor: "#ffffff", }, }, }} /> onChange(e.target.value)} size="small" disabled={isListening} helperText={ error ? error : isListening ? "Listening... Speak your topic now." : isUrl ? "URL detected. We'll analyze this page content." : "Enter a clear, concise topic. You can also click the mic to speak." } sx={{ "& .MuiOutlinedInput-root": { backgroundColor: isListening ? "rgba(16, 185, 129, 0.04)" : "#f8fafc", border: isListening ? "2px solid rgba(16, 185, 129, 0.5)" : "2px solid rgba(102, 126, 234, 0.2)", borderRadius: 2, fontSize: "1rem", transition: "all 0.2s ease", "&:hover": { backgroundColor: "#ffffff", borderColor: isListening ? "rgba(16, 185, 129, 0.7)" : "rgba(102, 126, 234, 0.4)", boxShadow: isListening ? "0 2px 8px rgba(16, 185, 129, 0.15)" : "0 2px 8px rgba(102, 126, 234, 0.1)", }, "&.Mui-focused": { backgroundColor: "#ffffff", borderColor: isListening ? "#10b981" : isUrl ? "#10b981" : "#667eea", borderWidth: 2, boxShadow: isListening ? "0 0 0 4px rgba(16, 185, 129, 0.1)" : isUrl ? "0 0 0 4px rgba(16, 185, 129, 0.1)" : "0 0 0 4px rgba(102, 126, 234, 0.1)", }, }, "& .MuiOutlinedInput-input": { fontSize: "1rem", lineHeight: 1.7, color: "#1e293b", fontWeight: 500, "&::placeholder": { color: "#64748b", opacity: 1, fontWeight: 400, }, }, "& .MuiFormHelperText-root": { color: error ? "#ef4444" : isListening ? "#059669" : isUrl ? "#059669" : "#64748b", fontSize: "0.8125rem", fontWeight: 500, mt: 1, }, }} /> {/* Mic button with listening indicator - positioned inside the textarea bottom-right */} {isSupported && !loading && ( {isListening && ( Listening... )} {isListening ? ( ) : ( )} )} {/* Category Research Chips - News + Finance + Research Papers + Personal Website */} {showAIDetailsButton && !isUrl && onCategoryResearchClick && ( : } label="News" onClick={() => onCategoryResearchClick("news")} disabled={categoryResearchLoading || loading} size="small" 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.3)", fontWeight: 600, fontSize: "0.8125rem", "&:hover": { background: "linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%)", transform: "scale(1.02)", }, }} /> : } label="Finance" onClick={() => onCategoryResearchClick("finance")} disabled={categoryResearchLoading || loading} size="small" sx={{ background: "linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%)", color: "#10b981", border: "1px solid rgba(16, 185, 129, 0.3)", fontWeight: 600, fontSize: "0.8125rem", "&:hover": { background: "linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.2) 100%)", transform: "scale(1.02)", }, }} /> : } label="Research Papers" onClick={() => onCategoryResearchClick("research-paper")} disabled={categoryResearchLoading || loading} size="small" sx={{ background: "linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(124, 58, 237, 0.1) 100%)", color: "#8b5cf6", border: "1px solid rgba(139, 92, 246, 0.3)", fontWeight: 600, fontSize: "0.8125rem", "&:hover": { background: "linear-gradient(135deg, rgba(139, 92, 246, 0.2) 0%, rgba(124, 58, 237, 0.2) 100%)", transform: "scale(1.02)", }, }} /> : } label="Personal Site" onClick={() => onCategoryResearchClick("personal-site", value)} disabled={categoryResearchLoading || loading} size="small" sx={{ background: "linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.1) 100%)", color: "#f59e0b", border: "1px solid rgba(245, 158, 11, 0.3)", fontWeight: 600, fontSize: "0.8125rem", "&:hover": { background: "linear-gradient(135deg, rgba(245, 158, 11, 0.2) 0%, rgba(217, 119, 6, 0.2) 100%)", transform: "scale(1.02)", }, }} /> )} {/* 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..."} )} {/* Website Preview Modal */} { setShowPreviewModal(false); setShowWebsiteInput(false); setWebsiteUrl(""); }} onUseTextOnly={() => { if (extractedData?.summary) { const newValue = extractedData.title ? `${extractedData.title}: ${extractedData.summary}` : extractedData.summary; onChange(newValue); } setShowPreviewModal(false); setShowWebsiteInput(false); setWebsiteUrl(""); }} onAnalyzeContent={() => { // Phase 2: Will trigger full website analysis console.log("[TopicUrlInput] Analyze Content clicked - Phase 2 feature"); }} /> ); };