import { useState, useEffect, useCallback } from 'react'; import { BlogOutlineSection, BlogResearchResponse, BlogSEOMetadataResponse, BlogSEOAnalyzeResponse, SourceMappingStats, GroundingInsights, OptimizationResults, ResearchCoverage } from '../services/blogWriterApi'; import { researchCache } from '../services/researchCache'; const MINOR_TITLE_WORDS = new Set([ 'a', 'an', 'and', 'or', 'but', 'the', 'for', 'nor', 'on', 'at', 'to', 'from', 'by', 'of', 'in', 'with', 'as', 'vs', 'vs.', 'into', 'over', 'under' ]); export const useBlogWriterState = () => { // Core state const [research, setResearch] = useState(null); const [outline, setOutline] = useState([]); const [titleOptions, setTitleOptions] = useState([]); const [selectedTitle, setSelectedTitle] = useState(''); const [sections, setSections] = useState>({}); const [seoAnalysis, setSeoAnalysis] = useState(null); const [genMode, setGenMode] = useState<'draft' | 'polished'>('polished'); const [seoMetadata, setSeoMetadata] = useState(null); const [continuityRefresh, setContinuityRefresh] = useState(0); const [outlineTaskId, setOutlineTaskId] = useState(null); const [flowAnalysisCompleted, setFlowAnalysisCompleted] = useState(false); const [flowAnalysisResults, setFlowAnalysisResults] = useState(null); // Enhanced metadata state const [sourceMappingStats, setSourceMappingStats] = useState(null); const [groundingInsights, setGroundingInsights] = useState(null); const [optimizationResults, setOptimizationResults] = useState(null); const [researchCoverage, setResearchCoverage] = useState(null); // Separate research titles from AI-generated titles const [researchTitles, setResearchTitles] = useState([]); const [aiGeneratedTitles, setAiGeneratedTitles] = useState([]); // Outline confirmation state const [outlineConfirmed, setOutlineConfirmed] = useState(false); // Content confirmation state const [contentConfirmed, setContentConfirmed] = useState(false); // Section images state - persists images generated in outline phase to content phase const [sectionImages, setSectionImages] = useState>({}); const formatContentAngleToTitle = useCallback((angle: string): string => { if (!angle || typeof angle !== 'string') { return ''; } const cleaned = angle.replace(/\s+/g, ' ').trim(); if (!cleaned) { return ''; } const words = cleaned.split(' '); const formattedWords = words.map((word, index) => { const lower = word.toLowerCase(); if (index !== 0 && MINOR_TITLE_WORDS.has(lower)) { return lower; } if (!lower) { return ''; } return lower.charAt(0).toUpperCase() + lower.slice(1); }).filter(Boolean); let formatted = formattedWords.join(' '); if (formatted.length > 120) { formatted = formatted.slice(0, 117).trimEnd() + '...'; } return formatted; }, []); const dedupeTitles = useCallback((titles: string[]): string[] => { const seen = new Set(); const result: string[] = []; titles.forEach((title) => { if (!title) { return; } const normalized = title.replace(/\s+/g, ' ').trim(); if (!normalized) { return; } const key = normalized.toLowerCase(); if (seen.has(key)) { return; } seen.add(key); result.push(normalized); }); return result; }, []); // Cache recovery - restore most recent research on page load useEffect(() => { const cachedEntries = researchCache.getAllCachedEntries(); if (cachedEntries.length > 0) { // Get the most recent cached research const mostRecent = cachedEntries[0]; console.log('Restoring cached research from page load:', mostRecent.keywords); setResearch(mostRecent.result); // Also try to restore outline if it exists in localStorage try { const savedOutline = localStorage.getItem('blog_outline'); const savedTitleOptions = localStorage.getItem('blog_title_options'); const savedSelectedTitle = localStorage.getItem('blog_selected_title'); if (savedOutline) { setOutline(JSON.parse(savedOutline)); } if (savedTitleOptions) { setTitleOptions(JSON.parse(savedTitleOptions)); } if (savedSelectedTitle) { setSelectedTitle(savedSelectedTitle); } console.log('Restored outline and title data from localStorage'); } catch (error) { console.error('Error restoring outline data:', error); } } }, []); // Handle research completion const handleResearchComplete = useCallback((researchData: BlogResearchResponse) => { setResearch(researchData); const formattedAngles = dedupeTitles( (researchData?.suggested_angles || []).map(formatContentAngleToTitle) ); setResearchTitles(formattedAngles); // Prefill title from research if no title is currently selected if (!selectedTitle && formattedAngles.length > 0) { const firstTitle = formattedAngles[0]; setSelectedTitle(firstTitle); localStorage.setItem('blog_selected_title', firstTitle); } }, [dedupeTitles, formatContentAngleToTitle, selectedTitle]); // Handle outline completion with enhanced metadata const handleOutlineComplete = useCallback((result: any) => { if (result?.outline) { setOutline(result.outline); const aiTitleOptions: string[] = result.title_options || []; const formattedAngles = dedupeTitles( (research?.suggested_angles || []).map(formatContentAngleToTitle) ); const combinedTitleOptions = dedupeTitles([ ...formattedAngles, ...aiTitleOptions ]); setTitleOptions(combinedTitleOptions); setResearchTitles(formattedAngles); const aiTitlesList = dedupeTitles( aiTitleOptions.filter((title: string) => !formattedAngles.some(angle => angle.toLowerCase() === (title || '').toLowerCase().trim())) ); setAiGeneratedTitles(aiTitlesList); const nextSelectedTitle = aiTitlesList[0] || formattedAngles[0] || combinedTitleOptions[0] || ''; if (nextSelectedTitle) { setSelectedTitle(nextSelectedTitle); } // Store enhanced metadata if (result.source_mapping_stats) { setSourceMappingStats(result.source_mapping_stats); } if (result.grounding_insights) { setGroundingInsights(result.grounding_insights); } if (result.optimization_results) { setOptimizationResults(result.optimization_results); } if (result.research_coverage) { setResearchCoverage(result.research_coverage); } // Save to localStorage for persistence (using shared cache utility) try { const { blogWriterCache } = require('../services/blogWriterCache'); blogWriterCache.cacheOutline(result.outline, combinedTitleOptions); localStorage.setItem('blog_title_options', JSON.stringify(combinedTitleOptions)); localStorage.setItem('blog_selected_title', nextSelectedTitle || ''); console.log('Saved outline data to localStorage'); } catch (error) { console.error('Error saving outline data:', error); } } setOutlineTaskId(null); // Reset outline confirmation when new outline is generated setOutlineConfirmed(false); }, [research, dedupeTitles, formatContentAngleToTitle]); // Handle outline error const handleOutlineError = useCallback((error: any) => { console.error('Outline generation error:', error); setOutlineTaskId(null); }, []); // Handle section generation const handleSectionGenerated = useCallback((sectionId: string, markdown: string) => { setSections(prev => ({ ...prev, [sectionId]: markdown })); }, []); // Handle continuity refresh const handleContinuityRefresh = useCallback(() => { setContinuityRefresh(Date.now()); }, []); // Handle title selection const handleTitleSelect = useCallback((title: string) => { setSelectedTitle(title); localStorage.setItem('blog_selected_title', title); }, []); // Handle custom title const handleCustomTitle = useCallback((title: string) => { const newTitleOptions = [...titleOptions, title]; setTitleOptions(newTitleOptions); setSelectedTitle(title); localStorage.setItem('blog_title_options', JSON.stringify(newTitleOptions)); localStorage.setItem('blog_selected_title', title); }, [titleOptions]); // Handle outline confirmation const handleOutlineConfirmed = useCallback(() => { setOutlineConfirmed(true); console.log('Outline confirmed by user'); }, []); // Handle outline refinement const handleOutlineRefined = useCallback((feedback: string) => { console.log('Outline refinement requested with feedback:', feedback); // The actual refinement will be handled by the copilot action }, []); // Handle content updates from WYSIWYG editor const handleContentUpdate = useCallback((updatedSections: any[]) => { console.log('Content updated:', updatedSections); // Update sections state with new content const newSections: { [key: string]: string } = {}; updatedSections.forEach(section => { newSections[section.id] = section.content; }); setSections(newSections); }, [setSections]); // Handle content saving const handleContentSave = useCallback((content: any) => { console.log('Content saved:', content); // Here you could save to backend or local storage // For now, just log the content }, []); return { // State research, outline, titleOptions, selectedTitle, sections, seoAnalysis, genMode, seoMetadata, continuityRefresh, outlineTaskId, sourceMappingStats, groundingInsights, optimizationResults, researchCoverage, researchTitles, aiGeneratedTitles, outlineConfirmed, contentConfirmed, flowAnalysisCompleted, flowAnalysisResults, sectionImages, // Setters setResearch, setOutline, setTitleOptions, setSelectedTitle, setSections, setSeoAnalysis, setGenMode, setSeoMetadata, setContinuityRefresh, setOutlineTaskId, setSourceMappingStats, setGroundingInsights, setOptimizationResults, setResearchCoverage, setResearchTitles, setAiGeneratedTitles, setOutlineConfirmed, setContentConfirmed, setFlowAnalysisCompleted, setFlowAnalysisResults, setSectionImages, // Handlers handleResearchComplete, handleOutlineComplete, handleOutlineError, handleSectionGenerated, handleContinuityRefresh, handleTitleSelect, handleCustomTitle, handleOutlineConfirmed, handleOutlineRefined, handleContentUpdate, handleContentSave }; };