import React, { useState, useEffect } from "react"; import { Stack, Box, Typography, Divider, Chip, Paper, alpha, CircularProgress, Button, Checkbox } from "@mui/material"; import { Psychology as PsychologyIcon, Insights as InsightsIcon, Search as SearchIcon, Person as PersonIcon, AutoAwesome as AutoAwesomeIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Add as AddIcon, EditNote as EditNoteIcon, Input as InputIcon, Groups as GroupsIcon, ListAlt as ListAltIcon, RecordVoiceOver as VoiceIcon, Lightbulb as TipsIcon, Quiz as TalkIcon } from "@mui/icons-material"; import { PodcastAnalysis, PodcastEstimate } 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, TitlesTab, HookTab, TakeawaysTab, GuestTab, CTATab } from "./AnalysisPanel/tabs"; interface AnalysisPanelProps { analysis: PodcastAnalysis | null; estimate: PodcastEstimate | null; idea?: string; duration?: number; speakers?: number; avatarUrl?: string | null; avatarPrompt?: string | null; onRegenerate?: () => void; onUpdateAnalysis?: (updatedAnalysis: PodcastAnalysis) => void; onRunResearch?: () => void; isResearchRunning?: boolean; selectedQueries?: Set; onToggleQuery?: (queryId: string) => void; queries?: { id: string; query: string; rationale: string }[]; } type TabId = 'inputs' | 'audience' | 'content' | 'outline' | 'titles' | 'hook' | 'takeaways' | 'cta' | '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, avatarUrl, avatarPrompt, onRegenerate, onUpdateAnalysis, onRunResearch, isResearchRunning, selectedQueries, onToggleQuery, queries }) => { const [activeTab, setActiveTab] = useState('inputs'); const [avatarBlobUrl, setAvatarBlobUrl] = useState(null); const [avatarLoading, setAvatarLoading] = useState(false); const [avatarError, setAvatarError] = useState(false); // Edit states const [isEditing, setIsEditing] = useState(false); const [editedAnalysis, setEditedAnalysis] = useState(null); const tabs: TabConfig[] = [ { id: 'inputs', label: 'Your Inputs', icon: }, { id: 'audience', label: 'Audience', icon: }, { id: 'content', label: 'Content', icon: }, { id: 'outline', label: 'Outline', icon: }, { id: 'titles', label: 'Titles', icon: }, { id: 'hook', label: 'Hook', icon: }, { id: 'takeaways', label: 'Takeaways', icon: }, { id: 'guest', label: 'Guest', icon: }, { id: 'cta', label: 'CTA', icon: }, ]; const tabButtonStyles = (isActive: boolean) => ({ background: isActive ? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)" : "transparent", color: isActive ? "#fff" : "#64748b", border: isActive ? "none" : "1px solid rgba(0,0,0,0.1)", borderRadius: 2, px: 2, py: 1, fontSize: "0.75rem", fontWeight: 600, textTransform: "none" as const, transition: "all 0.2s ease", "&:hover": { background: isActive ? "linear-gradient(135deg, #764ba2 0%, #667eea 100%)" : "rgba(102,126,234,0.08)", }, }); // Sync editedAnalysis with analysis initially useEffect(() => { if (analysis && !editedAnalysis) { setEditedAnalysis(JSON.parse(JSON.stringify(analysis))); } }, [analysis, editedAnalysis]); const handleSave = () => { if (editedAnalysis && onUpdateAnalysis) { console.log('[AnalysisPanel] Saving updated analysis:', editedAnalysis); 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; console.log('[AnalysisPanel] Rendering:', { isEditing, hasEditedAnalysis: !!editedAnalysis }); return ( AI Analysis {estimate && ( Est. Cost: ${estimate.total.toFixed(2)} )} {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 === 'titles' && ( )} {activeTab === 'hook' && ( )} {activeTab === 'takeaways' && ( )} {activeTab === 'guest' && ( )} {activeTab === 'cta' && ( )} {/* Research Section - Separate from tabs */} Research Queries {selectedQueries && selectedQueries.size > 0 && ( )} {onRunResearch && ( )} {!analysis?.research_queries || analysis.research_queries.length === 0 ? ( No research queries yet. Click "Regenerate Analysis" to generate research queries based on your podcast idea. ) : ( {(queries || analysis.research_queries?.map((rq, idx) => ({ id: `query-${idx}`, ...rq }))).map((rq: { id: string; query: string; rationale: string }, idx: number) => { const queryId = rq.id; const isSelected = selectedQueries?.has(queryId) || false; return ( onToggleQuery?.(queryId)} > onToggleQuery?.(queryId)} sx={{ color: "#64748b", "&.Mui-checked": { color: "#4f46e5", }, padding: 0.5, }} /> {rq.query} Rationale: {rq.rationale} ); })} )} ); };