story writer backend migration complete, Blog writer SEO and story writer backend migration complete, Blog writer SEO and story writer frontend migration complete

This commit is contained in:
ajaysi
2025-11-13 16:14:26 +05:30
parent 7191c7e7f0
commit 3b9356e2c8
124 changed files with 20055 additions and 1208 deletions

View File

@@ -2,6 +2,11 @@ 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<BlogResearchResponse | null>(null);
@@ -36,6 +41,57 @@ export const useBlogWriterState = () => {
// Section images state - persists images generated in outline phase to content phase
const [sectionImages, setSectionImages] = useState<Record<string, string>>({});
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<string>();
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();
@@ -71,14 +127,46 @@ export const useBlogWriterState = () => {
// 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);
setTitleOptions(result.title_options || []);
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);
@@ -92,35 +180,13 @@ export const useBlogWriterState = () => {
if (result.research_coverage) {
setResearchCoverage(result.research_coverage);
}
// Separate research titles from AI-generated titles
if (result.title_options && research) {
const researchAngles = research.suggested_angles || [];
const researchTitlesList = result.title_options.filter((title: string) =>
researchAngles.some((angle: string) => title.toLowerCase().includes(angle.toLowerCase().substring(0, 20)))
);
const aiTitlesList = result.title_options.filter((title: string) =>
!researchTitlesList.includes(title)
);
setResearchTitles(researchTitlesList);
setAiGeneratedTitles(aiTitlesList);
// Auto-select first AI-generated title if available, otherwise first research title
if (aiTitlesList.length > 0) {
setSelectedTitle(aiTitlesList[0]);
} else if (researchTitlesList.length > 0) {
setSelectedTitle(researchTitlesList[0]);
} else if (result.title_options.length > 0) {
setSelectedTitle(result.title_options[0]);
}
}
// Save to localStorage for persistence (using shared cache utility)
try {
const { blogWriterCache } = require('../services/blogWriterCache');
blogWriterCache.cacheOutline(result.outline, result.title_options);
localStorage.setItem('blog_selected_title', result.title_options?.[0] || '');
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);
@@ -129,7 +195,7 @@ export const useBlogWriterState = () => {
setOutlineTaskId(null);
// Reset outline confirmation when new outline is generated
setOutlineConfirmed(false);
}, [research]);
}, [research, dedupeTitles, formatContentAngleToTitle]);
// Handle outline error
const handleOutlineError = useCallback((error: any) => {