import React, { useState, useEffect } from "react"; import { Stack, Box, Typography, Divider, Chip, alpha, Button, IconButton, Popover, TextField, Tooltip } from "@mui/material"; import PsychologyIcon from "@mui/icons-material/Psychology"; import PersonIcon from "@mui/icons-material/Person"; import EditIcon from "@mui/icons-material/Edit"; import SaveIcon from "@mui/icons-material/Save"; import CloseIcon from "@mui/icons-material/Close"; import InputIcon from "@mui/icons-material/Input"; import GroupsIcon from "@mui/icons-material/Groups"; import ListAltIcon from "@mui/icons-material/ListAlt"; import TipsIcon from "@mui/icons-material/Lightbulb"; import ArticleIcon from "@mui/icons-material/Article"; import BibleIcon from "@mui/icons-material/AutoFixHigh"; import RefreshIcon from "@mui/icons-material/Refresh"; import { PodcastAnalysis, PodcastEstimate, PodcastBible } from "./types"; import { GlassyCard, glassyCardSx, SecondaryButton } from "./ui"; import { aiApiClient } from "../../api/client"; import { InputsTab, AudienceTab, OutlineTab, EpisodeDetailsTab, TakeawaysTab, GuestTab } from "./AnalysisPanel/tabs"; interface AnalysisPanelProps { analysis: PodcastAnalysis | null; estimate: PodcastEstimate | null; idea?: string; duration?: number; speakers?: number; voiceName?: string; podcastMode?: "audio_only" | "video_only" | "audio_video"; avatarUrl?: string | null; avatarPrompt?: string | null; bible?: PodcastBible | null; onRegenerate?: () => void; onUpdateAnalysis?: (updatedAnalysis: PodcastAnalysis) => void; onUpdateBible?: (updatedBible: PodcastBible) => void; } type TabId = 'inputs' | 'audience' | 'outline' | 'details' | 'takeaways' | 'guest'; interface TabConfig { id: TabId; label: string; icon: React.ReactNode; } const inputStyles = { '& .MuiInputBase-input': { color: '#111827 !important', fontWeight: 500, WebkitTextFillColor: '#111827 !important', // Fix for some browsers }, '& .MuiInputLabel-root': { color: '#4b5563 !important', }, '& .MuiOutlinedInput-root': { bgcolor: '#ffffff !important', '& fieldset': { borderColor: '#d1d5db !important', }, '&:hover fieldset': { borderColor: '#4f46e5 !important', }, '&.Mui-focused fieldset': { borderColor: '#4f46e5 !important', } }, '& .MuiSelect-select': { color: '#111827 !important', WebkitTextFillColor: '#111827 !important', } }; export const AnalysisPanel: React.FC = ({ analysis, estimate, idea, duration, speakers, voiceName, podcastMode, avatarUrl, avatarPrompt, bible, onRegenerate, onUpdateAnalysis, onUpdateBible }) => { const [activeTab, setActiveTab] = useState('inputs'); const [avatarBlobUrl, setAvatarBlobUrl] = useState(null); const [avatarLoading, setAvatarLoading] = useState(false); const [avatarError, setAvatarError] = useState(false); const [bibleAnchorEl, setBibleAnchorEl] = useState(null); // Edit states const [isEditing, setIsEditing] = useState(false); const [editedAnalysis, setEditedAnalysis] = useState(null); const [editedBible, setEditedBible] = useState(null); const tabs: TabConfig[] = [ { id: 'inputs', label: 'Your Inputs', icon: }, { id: 'audience', label: 'Audience & Keywords', icon: }, { id: 'outline', label: 'Outline', icon: }, { id: 'details', label: 'Titles, Hook & CTA', icon: }, { id: 'takeaways', label: 'Takeaways', icon: }, { id: 'guest', label: 'Guest Talking Points', icon: }, ]; const tabButtonStyles = (isActive: boolean) => ({ background: isActive ? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)" : "#f8fafc", color: isActive ? "#fff" : "#475569", border: isActive ? "none" : "1px solid #e2e8f0", borderRadius: 2.5, px: 2.5, py: 1.25, fontSize: "0.8rem", fontWeight: 600, textTransform: "none" as const, transition: "all 0.25s ease", boxShadow: isActive ? "0 4px 12px rgba(102, 126, 234, 0.3)" : "0 1px 2px rgba(0, 0, 0, 0.05)", "&:hover": { background: isActive ? "linear-gradient(135deg, #764ba2 0%, #667eea 100%)" : "#e2e8f0", transform: isActive ? "translateY(-1px)" : "none", boxShadow: isActive ? "0 6px 16px rgba(102, 126, 234, 0.35)" : "0 2px 4px rgba(0, 0, 0, 0.08)", }, "&:active": { transform: "translateY(0)", }, }); // Sync editedAnalysis with analysis initially useEffect(() => { if (analysis && !editedAnalysis) { setEditedAnalysis(JSON.parse(JSON.stringify(analysis))); } }, [analysis, editedAnalysis]); const handleSave = () => { if (editedAnalysis && onUpdateAnalysis) { onUpdateAnalysis(JSON.parse(JSON.stringify(editedAnalysis))); } setIsEditing(false); }; const handleCancel = () => { setIsEditing(false); setEditedAnalysis(JSON.parse(JSON.stringify(analysis))); }; const updateExaConfig = (field: string, value: any) => { if (!editedAnalysis) return; setEditedAnalysis({ ...editedAnalysis, exaSuggestedConfig: { ...(editedAnalysis.exaSuggestedConfig || {}), [field]: value } }); }; const handleAddKeyword = (keyword: string) => { if (!editedAnalysis || !keyword.trim()) return; if (editedAnalysis.topKeywords.includes(keyword.trim())) return; setEditedAnalysis({ ...editedAnalysis, topKeywords: [...editedAnalysis.topKeywords, keyword.trim()] }); }; const handleRemoveKeyword = (keyword: string) => { if (!editedAnalysis) return; setEditedAnalysis({ ...editedAnalysis, topKeywords: editedAnalysis.topKeywords.filter(k => k !== keyword) }); }; const handleAddTitle = (title: string) => { if (!editedAnalysis || !title.trim()) return; setEditedAnalysis({ ...editedAnalysis, titleSuggestions: [...editedAnalysis.titleSuggestions, title.trim()] }); }; const handleRemoveTitle = (title: string) => { if (!editedAnalysis) return; setEditedAnalysis({ ...editedAnalysis, titleSuggestions: editedAnalysis.titleSuggestions.filter(t => t !== title) }); }; const handleUpdateOutline = (id: string | number, field: 'title' | 'segments', value: any) => { if (!editedAnalysis) return; setEditedAnalysis({ ...editedAnalysis, suggestedOutlines: editedAnalysis.suggestedOutlines.map(o => o.id === id ? { ...o, [field]: value } : o ) }); }; // Load avatar image as blob for authenticated URLs useEffect(() => { if (!avatarUrl) { setAvatarBlobUrl(null); setAvatarError(false); return; } // Check if it's already a blob URL if (avatarUrl.startsWith('blob:')) { setAvatarBlobUrl(avatarUrl); return; } // Check if it's an authenticated endpoint const isAuthenticatedEndpoint = avatarUrl.includes('/api/podcast/images/') || avatarUrl.includes('/api/podcast/avatar/'); let currentBlobUrl: string | null = null; if (isAuthenticatedEndpoint) { setAvatarLoading(true); setAvatarError(false); const loadAvatarBlob = async () => { try { const response = await aiApiClient.get(avatarUrl, { responseType: 'blob' }); const blobUrl = URL.createObjectURL(response.data); currentBlobUrl = blobUrl; setAvatarBlobUrl(blobUrl); setAvatarError(false); } catch (error) { console.error('[AnalysisPanel] Failed to load avatar as blob:', error); // Fallback: try with query token try { const token = localStorage.getItem('clerk_dashboard_token') || ''; if (token) { const urlWithToken = `${avatarUrl}?token=${encodeURIComponent(token)}`; setAvatarBlobUrl(urlWithToken); } else { setAvatarError(true); } } catch (fallbackError) { console.error('[AnalysisPanel] Fallback avatar loading failed:', fallbackError); setAvatarError(true); } } finally { setAvatarLoading(false); } }; loadAvatarBlob(); // Cleanup blob URL on unmount or when avatarUrl changes return () => { if (currentBlobUrl && currentBlobUrl.startsWith('blob:')) { URL.revokeObjectURL(currentBlobUrl); } // Also cleanup any previous blob URL from state setAvatarBlobUrl((prev) => { if (prev && prev.startsWith('blob:') && prev !== currentBlobUrl) { URL.revokeObjectURL(prev); } return null; }); }; } else { // Direct URL, use as-is setAvatarBlobUrl(avatarUrl); } }, [avatarUrl]); if (!analysis) return null; const currentAnalysis = isEditing && editedAnalysis ? editedAnalysis : analysis; return ( AI Analysis {estimate && ( Est. Cost: ${estimate.total.toFixed(2)} )} {/* Bible Button */} {bible && ( 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", }, }} > )} {isEditing ? ( <> } sx={{ color: '#059669', borderColor: '#10b981', bgcolor: 'white', fontWeight: 600, '&:hover': { bgcolor: alpha('#10b981', 0.05) } }} > Save Changes } sx={{ color: '#4b5563', borderColor: '#d1d5db', bgcolor: 'white' }} > Cancel ) : ( <> setIsEditing(true)} startIcon={} sx={{ color: '#4f46e5', borderColor: '#4f46e5', bgcolor: 'white', fontWeight: 600 }} > Edit Analysis } tooltip="Regenerate analysis with different parameters" sx={{ color: '#4b5563', borderColor: '#d1d5db', bgcolor: 'white' }} > Regenerate )} {/* AI Futuristic Tab Navigation */} {tabs.map((tab) => ( ))} {/* Tab Content */} {activeTab === 'inputs' && ( )} {activeTab === 'audience' && ( )} {activeTab === 'outline' && ( )} {activeTab === 'details' && ( )} {activeTab === 'takeaways' && ( )} {activeTab === 'guest' && ( )} {/* Bible Popover */} 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)", }, }} > Podcast Bible ℹ️ {/* Host Persona */} Host Persona {bible?.host?.name || "Not set"} • {bible?.host?.background || "No background"} • {bible?.host?.vocal_style || "No style"} {/* Audience DNA */} Audience DNA {bible?.audience?.expertise_level || "General"} • {(bible?.audience?.interests || []).slice(0, 3).join(", ") || "Various interests"} {/* Brand DNA */} Brand DNA {bible?.brand?.industry || "No industry"} • {bible?.brand?.tone || "No tone"} • {bible?.brand?.communication_style || "No style"} Podcast Bible personalizes all AI generation for your unique voice ); };