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

@@ -146,19 +146,6 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
}
}, [isOpen]);
// Auto-generate metadata when modal opens (only once)
const hasAutoGeneratedRef = React.useRef(false);
useEffect(() => {
if (isOpen && blogContent && !hasAutoGeneratedRef.current) {
hasAutoGeneratedRef.current = true;
generateMetadata(false); // Auto-generate from cache or API
}
if (!isOpen) {
hasAutoGeneratedRef.current = false; // Reset when modal closes
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]); // Only trigger when modal opens
const generateMetadata = useCallback(async (forceRefresh = false) => {
try {
setIsGenerating(true);
@@ -169,10 +156,15 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
console.log('🚀 Starting SEO metadata generation...', { forceRefresh });
// Calculate content hash for caching
const hash = await hashContent(`${blogTitle || ''}\n${blogContent}`);
setContentHash(hash);
// Calculate content hash for caching - use existing hash if available
let hash = contentHash;
if (!hash) {
hash = await hashContent(`${blogTitle || ''}\n${blogContent}`);
// Update state for future use
setContentHash(hash);
}
const cacheKey = getMetadataCacheKey(hash, blogTitle);
console.log('🔍 Checking SEO metadata cache', { cacheKey, hasHash: !!hash, forceRefresh });
// Check cache first (unless force refresh)
if (!forceRefresh && typeof window !== 'undefined') {
@@ -180,15 +172,32 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
if (cached) {
try {
const parsed = JSON.parse(cached) as SEOMetadataResult;
console.log('✅ Using cached SEO metadata');
setMetadataResult(parsed);
setEditableMetadata(parsed);
setIsGenerating(false);
return;
// Validate cached data has required fields
if (parsed && parsed.success !== undefined) {
console.log('✅ Using cached SEO metadata', { cacheKey, success: parsed.success });
setMetadataResult(parsed);
setEditableMetadata(parsed);
setIsGenerating(false);
// Notify parent that metadata is available
if (onMetadataGenerated) {
onMetadataGenerated(parsed);
}
return;
} else {
console.warn('⚠️ Cached SEO metadata data is invalid, will fetch fresh metadata');
}
} catch (e) {
console.warn('Failed to parse cached metadata:', e);
console.warn('⚠️ Failed to parse cached SEO metadata, will fetch fresh metadata', e);
// Remove invalid cache entry
if (typeof window !== 'undefined') {
window.localStorage.removeItem(cacheKey);
}
}
} else {
console.log(' No cached SEO metadata found, will fetch from API', { cacheKey });
}
} else {
console.log('🔄 Force refresh requested, skipping cache check');
}
// Make API call to generate metadata
@@ -203,7 +212,43 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
const result = response.data;
console.log('✅ SEO metadata generation response:', result);
if (!result.success) {
// Check if the response indicates a subscription error (even if HTTP status is 200)
if (!result.success && result.error) {
const errorMessage = result.error;
// Check if error message indicates subscription limit (429/402)
if (errorMessage.includes('Token limit') ||
errorMessage.includes('limit would be exceeded') ||
errorMessage.includes('usage limit') ||
errorMessage.includes('subscription')) {
console.log('SEOMetadataModal: Detected subscription error in response data', {
error: errorMessage,
data: result
});
// Create a mock error object with subscription error data
const mockError = {
response: {
status: 429, // Treat as 429 for subscription error
data: {
error: errorMessage,
message: result.message || errorMessage,
provider: result.provider || 'unknown',
usage_info: result.usage_info || {}
}
}
};
const handled = await triggerSubscriptionError(mockError);
if (handled) {
console.log('SEOMetadataModal: Global subscription error handler triggered successfully');
setIsGenerating(false);
return;
} else {
console.warn('SEOMetadataModal: Global subscription error handler did not handle the error');
}
}
// If not a subscription error, throw the error normally
throw new Error(result.error || 'Metadata generation failed');
}
@@ -226,15 +271,51 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
// Check if this is a subscription error (429/402) and trigger global subscription modal
const status = err?.response?.status;
const errorMessage = err?.message || err?.response?.data?.error || '';
// Check HTTP status code first
if (status === 429 || status === 402) {
console.log('SEOMetadataModal: Detected subscription error, triggering global handler', {
console.log('SEOMetadataModal: Detected subscription error (HTTP status), triggering global handler', {
status,
data: err?.response?.data
});
const handled = await triggerSubscriptionError(err);
if (handled) {
console.log('SEOMetadataModal: Global subscription error handler triggered successfully');
// Don't set local error - let the global modal handle it
setIsGenerating(false);
return;
} else {
console.warn('SEOMetadataModal: Global subscription error handler did not handle the error');
}
}
// Also check error message for subscription-related errors (in case API returns 200 with error in body)
if (errorMessage.includes('Token limit') ||
errorMessage.includes('limit would be exceeded') ||
errorMessage.includes('usage limit') ||
errorMessage.includes('subscription') ||
errorMessage.includes('429')) {
console.log('SEOMetadataModal: Detected subscription error (error message), triggering global handler', {
errorMessage,
err
});
// Create a mock error object with subscription error data
const mockError = {
response: {
status: 429,
data: {
error: errorMessage,
message: errorMessage,
provider: err?.response?.data?.provider || 'unknown',
usage_info: err?.response?.data?.usage_info || {}
}
}
};
const handled = await triggerSubscriptionError(mockError);
if (handled) {
console.log('SEOMetadataModal: Global subscription error handler triggered successfully (from error message)');
setIsGenerating(false);
return;
} else {
@@ -247,7 +328,34 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
} finally {
setIsGenerating(false);
}
}, [blogContent, blogTitle, researchData, outline, seoAnalysis]);
}, [blogContent, blogTitle, researchData, outline, seoAnalysis, contentHash, onMetadataGenerated]);
// Precompute hash when modal opens and trigger cache check
useEffect(() => {
if (isOpen) {
(async () => {
const h = await hashContent(`${blogTitle || ''}\n${blogContent}`);
setContentHash(h);
// After hash is computed, check cache if we don't have metadata result yet
if (!metadataResult) {
// Small delay to ensure hash is set in state
setTimeout(() => {
generateMetadata(false);
}, 100);
}
})();
} else {
// Reset hash when modal closes
setContentHash('');
}
}, [isOpen, blogContent, blogTitle, metadataResult, generateMetadata]);
// Fallback: if modal opens and hash is already computed, check cache immediately
useEffect(() => {
if (isOpen && !metadataResult && contentHash) {
generateMetadata(false);
}
}, [isOpen, metadataResult, contentHash, generateMetadata]);
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setTabValue(newValue);