import React, { useState, useEffect } from "react"; 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, AutoFixHigh as BibleIcon } from "@mui/icons-material"; import { PodcastAnalysis, PodcastEstimate, PodcastBible } from "./types"; import { GlassyCard, glassyCardSx, SecondaryButton } from "./ui"; import { Refresh as RefreshIcon } from "@mui/icons-material"; 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 ); };