Added image generation to blog writer

This commit is contained in:
ajaysi
2025-10-31 15:59:16 +05:30
parent 3219e6bbe4
commit cdb41aec1b
80 changed files with 7662 additions and 3951 deletions

View File

@@ -0,0 +1,210 @@
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import { BlogResearchResponse, BlogOutlineSection } from '../services/blogWriterApi';
export interface Phase {
id: string;
name: string;
icon: string;
description: string;
completed: boolean;
current: boolean;
disabled: boolean;
}
export const usePhaseNavigation = (
research: BlogResearchResponse | null,
outline: BlogOutlineSection[],
outlineConfirmed: boolean,
hasContent: boolean,
contentConfirmed: boolean,
seoAnalysis: any,
seoMetadata: any,
seoRecommendationsApplied?: boolean
) => {
// Initialize from localStorage if available
const getInitialPhase = (): string => {
try {
if (typeof window !== 'undefined') {
const stored = window.localStorage.getItem('blogwriter_current_phase');
if (stored) return stored;
}
} catch {}
return 'research';
};
const [currentPhase, setCurrentPhase] = useState<string>(getInitialPhase());
const [userSelectedPhase, setUserSelectedPhase] = useState<boolean>(() => {
try {
if (typeof window !== 'undefined') {
const stored = window.localStorage.getItem('blogwriter_user_selected_phase');
return stored === 'true';
}
} catch {}
return false;
});
const lastClickAtRef = useRef<number>(0);
// Determine phase states based on current data
const phases = useMemo((): Phase[] => {
const researchCompleted = !!research;
const outlineCompleted = outline.length > 0;
const contentCompleted = hasContent && contentConfirmed;
// SEO is complete when analysis exists AND recommendations are applied
const seoCompleted = !!seoAnalysis && (seoRecommendationsApplied === true || !!seoMetadata);
return [
{
id: 'research',
name: 'Research',
icon: '🔍',
description: 'Research your topic and gather data',
completed: researchCompleted,
current: currentPhase === 'research',
disabled: false // Research is always accessible
},
{
id: 'outline',
name: 'Outline',
icon: '📝',
description: 'Create and refine your blog outline',
completed: outlineCompleted,
current: currentPhase === 'outline',
disabled: !researchCompleted // Disabled only if research not completed (can always go back if completed)
},
{
id: 'content',
name: 'Content',
icon: '✍️',
description: 'Generate and edit your blog content',
completed: contentCompleted,
current: currentPhase === 'content',
disabled: !outlineCompleted // Disabled only if outline not completed (can always go back if completed)
},
{
id: 'seo',
name: 'SEO',
icon: '📈',
description: 'Optimize for search engines',
completed: seoCompleted,
current: currentPhase === 'seo',
disabled: !contentCompleted // Disabled only if content not completed (can always go back if completed)
},
{
id: 'publish',
name: 'Publish',
icon: '🚀',
description: 'Publish your blog post',
completed: false, // This would be set when actually published
current: currentPhase === 'publish',
disabled: !seoCompleted // Can access if SEO done
}
];
}, [research, outline, outlineConfirmed, hasContent, contentConfirmed, seoAnalysis, seoMetadata, seoRecommendationsApplied, currentPhase]);
// Persist current phase and user selection
useEffect(() => {
try {
if (typeof window !== 'undefined') {
window.localStorage.setItem('blogwriter_current_phase', currentPhase);
window.localStorage.setItem('blogwriter_user_selected_phase', String(userSelectedPhase));
}
} catch {}
}, [currentPhase, userSelectedPhase]);
// Validate stored phase against current availability (quiet)
useEffect(() => {
const current = phases.find(p => p.id === currentPhase);
if (!current) {
setCurrentPhase('research');
return;
}
if (current.disabled) {
// Find the first non-disabled phase in order of progression the user qualifies for
const fallback = phases.find(p => !p.disabled) || ({ id: 'research' } as Phase);
if (fallback.id !== currentPhase) {
setCurrentPhase(fallback.id);
}
}
}, [phases, currentPhase]);
// Auto-update current phase based on completion status (only if user hasn't manually selected a phase)
useEffect(() => {
if (userSelectedPhase) {
return; // Don't auto-update if user has manually selected a phase
}
// Auto-progress to the next available phase when conditions are met
if (research && outline.length === 0) {
// Research completed, but no outline yet - stay on research
if (currentPhase !== 'research') {
setCurrentPhase('research');
}
} else if (research && outline.length > 0 && !outlineConfirmed) {
// Outline created but not confirmed - move to outline phase
if (currentPhase !== 'outline') {
setCurrentPhase('outline');
}
} else if (outlineConfirmed && hasContent && !contentConfirmed) {
// Content generated but not confirmed - move to content phase
if (currentPhase !== 'content') {
setCurrentPhase('content');
}
} else if (contentConfirmed && !seoAnalysis) {
// Content confirmed but no SEO analysis yet - move to SEO phase
if (currentPhase !== 'seo') {
setCurrentPhase('seo');
}
} else if (seoAnalysis && !seoRecommendationsApplied && !seoMetadata) {
// SEO analysis done but recommendations not applied - stay on SEO phase
if (currentPhase !== 'seo') {
setCurrentPhase('seo');
}
} else if (seoAnalysis && (seoRecommendationsApplied || seoMetadata)) {
// SEO recommendations applied or metadata generated
if (currentPhase === 'seo') {
// CRITICAL: Stay in SEO phase so user can review updated content - don't auto-progress
// User will manually navigate to publish when ready
// This prevents blank screen by keeping user in SEO phase where BlogEditor is visible
// No action needed - already in SEO phase, stay here
} else {
// User is NOT in SEO phase - can progress to publish
// This handles cases where user navigates away and comes back
// Only auto-progress if user is already in a different phase (not actively in SEO)
if (currentPhase !== 'publish') {
setCurrentPhase('publish');
}
}
}
}, [research, outline, outlineConfirmed, hasContent, contentConfirmed, seoAnalysis, seoMetadata, seoRecommendationsApplied, currentPhase, userSelectedPhase]);
const navigateToPhase = useCallback((phaseId: string) => {
// Minimal debounce (200ms) to avoid race conditions on rapid clicks
const now = Date.now();
if (now - lastClickAtRef.current < 200) { return; }
lastClickAtRef.current = now;
const phase = phases.find(p => p.id === phaseId);
if (phase && !phase.disabled) {
setCurrentPhase(phaseId);
setUserSelectedPhase(true); // Mark that user has manually selected a phase
} else {
// Quietly ignore blocked navigation
}
}, [phases, currentPhase]);
// Reset user selection when a new phase is completed (to allow auto-progression)
const resetUserSelection = () => {
setUserSelectedPhase(false);
};
return {
phases,
currentPhase,
navigateToPhase,
setCurrentPhase,
resetUserSelection
};
};
export default usePhaseNavigation;