final all done 2nd last plan before goign live

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 22:32:29 +00:00
parent 5f9a4b8dca
commit d0f98d35d6
19 changed files with 1581 additions and 233 deletions

View File

@@ -46,41 +46,47 @@ const getSuccessMessage = (functionId?: string, title?: string, stepLogs?: any[]
const clusterCount = extractCount(/(\d+)\s+cluster/i, stepLogs || []);
if (keywordCount && clusterCount) {
return `Clustering complete\n${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} mapped and grouped into ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`;
return `✓ Created ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} from ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''}`;
} else if (clusterCount) {
return `Clustering complete\n${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} created`;
return `✓ Created ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`;
} else if (keywordCount) {
return `Clustering complete\n${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} mapped and grouped into clusters`;
return `✓ Created clusters from ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''}`;
}
return 'Clustering complete\nKeywords mapped and grouped into clusters';
return '✓ Keywords clustered successfully';
}
if (funcName.includes('idea')) {
const ideaCount = extractCount(/(\d+)\s+idea/i, stepLogs || []);
const clusterCount = extractCount(/(\d+)\s+cluster/i, stepLogs || []);
if (ideaCount) {
return `Content ideas & outlines created successfully`;
if (ideaCount && clusterCount) {
return `✓ Generated ${ideaCount} content idea${ideaCount !== '1' ? 's' : ''} from ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`;
} else if (ideaCount) {
return `✓ Generated ${ideaCount} content idea${ideaCount !== '1' ? 's' : ''} with outlines`;
}
return 'Content ideas & outlines created successfully';
return 'Content ideas & outlines created successfully';
}
if (funcName.includes('content')) {
const taskCount = extractCount(/(\d+)\s+task/i, stepLogs || []);
const articleCount = extractCount(/(\d+)\s+article/i, stepLogs || []);
const wordCount = extractCount(/(\d+[,\d]*)\s+word/i, stepLogs || []);
if (articleCount) {
return `Article${articleCount !== '1' ? 's' : ''} drafted successfully — ${articleCount} article${articleCount !== '1' ? 's' : ''} generated.`;
if (articleCount && wordCount) {
return ` ${articleCount} article${articleCount !== '1' ? 's' : ''} generated (${wordCount} words total)`;
} else if (articleCount) {
return `${articleCount} article${articleCount !== '1' ? 's' : ''} generated`;
} else if (taskCount) {
return `Article${taskCount !== '1' ? 's' : ''} drafted successfully — ${taskCount} task${taskCount !== '1' ? 's' : ''} completed.`;
return `${taskCount} article${taskCount !== '1' ? 's' : ''} generated`;
}
return 'Article drafted successfully.';
return 'Article generated successfully';
}
// Check for image generation from prompts FIRST (more specific)
if (funcName.includes('image') && funcName.includes('from')) {
// Image generation from prompts
const imageCount = extractCount(/(\d+)\s+image/i, stepLogs || []);
if (imageCount) {
return `${imageCount} image${imageCount !== '1' ? 's' : ''} generated successfully`;
return `${imageCount} image${imageCount !== '1' ? 's' : ''} generated and saved`;
}
return 'Images generated successfully';
return 'Images generated and saved';
} else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) {
// Image prompt generation
// Try to extract from SAVE step message first (most reliable)
@@ -92,9 +98,9 @@ const getSuccessMessage = (functionId?: string, title?: string, stepLogs?: any[]
const totalPrompts = parseInt(countMatch[1], 10);
const inArticleCount = totalPrompts > 1 ? totalPrompts - 1 : 0;
if (inArticleCount > 0) {
return `Featured Image and ${inArticleCount} Inarticle Image Prompts ready for image generation`;
return `${totalPrompts} image prompts ready (1 featured + ${inArticleCount} in-article)`;
} else {
return `Featured Image Prompt ready for image generation`;
return `✓ 1 image prompt ready`;
}
}
}
@@ -107,9 +113,9 @@ const getSuccessMessage = (functionId?: string, title?: string, stepLogs?: any[]
const totalPrompts = parseInt(match[1], 10);
const inArticleCount = totalPrompts > 1 ? totalPrompts - 1 : 0;
if (inArticleCount > 0) {
return `Featured Image and ${inArticleCount} Inarticle Image Prompts ready for image generation`;
return `${totalPrompts} image prompts ready (1 featured + ${inArticleCount} in-article)`;
} else {
return `Featured Image Prompt ready for image generation`;
return `✓ 1 image prompt ready`;
}
}
}
@@ -120,49 +126,49 @@ const getSuccessMessage = (functionId?: string, title?: string, stepLogs?: any[]
const totalPrompts = parseInt(promptCount, 10);
const inArticleCount = totalPrompts > 1 ? totalPrompts - 1 : 0;
if (inArticleCount > 0) {
return `Featured Image and ${inArticleCount} Inarticle Image Prompts ready for image generation`;
return `${totalPrompts} image prompts ready (1 featured + ${inArticleCount} in-article)`;
} else {
return `Featured Image Prompt ready for image generation`;
return `✓ 1 image prompt ready`;
}
}
// Default message
return 'Image prompts ready for generation';
return 'Image prompts ready';
}
return 'Task completed successfully.';
return 'Task completed successfully';
};
// Get step definitions per function
// Get step definitions per function - these are default labels that get replaced with dynamic counts
const getStepsForFunction = (functionId?: string, title?: string): Array<{phase: string, label: string}> => {
const funcName = functionId?.toLowerCase() || title?.toLowerCase() || '';
if (funcName.includes('cluster')) {
return [
{ phase: 'INIT', label: 'Validating keywords' },
{ phase: 'PREP', label: 'Loading keyword data' },
{ phase: 'AI_CALL', label: 'Generating clusters with Igny8 Semantic SEO Model' },
{ phase: 'PARSE', label: 'Organizing clusters' },
{ phase: 'INIT', label: 'Validating keywords for clustering' },
{ phase: 'PREP', label: 'Analyzing keyword relationships' },
{ phase: 'AI_CALL', label: 'Grouping keywords by search intent' },
{ phase: 'PARSE', label: 'Organizing semantic clusters' },
{ phase: 'SAVE', label: 'Saving clusters' },
];
}
if (funcName.includes('idea')) {
return [
{ phase: 'INIT', label: 'Verifying cluster integrity' },
{ phase: 'PREP', label: 'Loading cluster keywords' },
{ phase: 'AI_CALL', label: 'Generating ideas with Igny8 Semantic AI' },
{ phase: 'PARSE', label: 'High-opportunity ideas generated' },
{ phase: 'SAVE', label: 'Content Outline for Ideas generated' },
{ phase: 'INIT', label: 'Analyzing clusters for content opportunities' },
{ phase: 'PREP', label: 'Mapping keywords to topic briefs' },
{ phase: 'AI_CALL', label: 'Generating content ideas' },
{ phase: 'PARSE', label: 'Structuring article outlines' },
{ phase: 'SAVE', label: 'Saving content ideas with outlines' },
];
}
if (funcName.includes('content')) {
return [
{ phase: 'INIT', label: 'Validating task' },
{ phase: 'PREP', label: 'Preparing content idea' },
{ phase: 'AI_CALL', label: 'Writing article with Igny8 Semantic AI' },
{ phase: 'PARSE', label: 'Formatting content' },
{ phase: 'SAVE', label: 'Saving article' },
{ phase: 'INIT', label: 'Preparing articles for generation' },
{ phase: 'PREP', label: 'Building content brief with target keywords' },
{ phase: 'AI_CALL', label: 'Writing articles with Igny8 Semantic AI' },
{ phase: 'PARSE', label: 'Formatting HTML content and metadata' },
{ phase: 'SAVE', label: 'Saving articles' },
];
}
@@ -170,20 +176,20 @@ const getStepsForFunction = (functionId?: string, title?: string): Array<{phase:
if (funcName.includes('image') && funcName.includes('from')) {
// Image generation from prompts
return [
{ phase: 'INIT', label: 'Validating image prompts' },
{ phase: 'PREP', label: 'Preparing image generation queue' },
{ phase: 'INIT', label: 'Queuing images for generation' },
{ phase: 'PREP', label: 'Preparing AI image generation' },
{ phase: 'AI_CALL', label: 'Generating images with AI' },
{ phase: 'PARSE', label: 'Processing image URLs' },
{ phase: 'SAVE', label: 'Saving image URLs' },
{ phase: 'PARSE', label: 'Processing generated images' },
{ phase: 'SAVE', label: 'Uploading images to media library' },
];
} else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) {
// Image prompt generation
return [
{ phase: 'INIT', label: 'Checking content and image slots' },
{ phase: 'PREP', label: 'Mapping content for image prompts' },
{ phase: 'AI_CALL', label: 'Writing Featured Image Prompts' },
{ phase: 'PARSE', label: 'Writing Inarticle Image Prompts' },
{ phase: 'SAVE', label: 'Assigning Prompts to Dedicated Slots' },
{ phase: 'INIT', label: 'Analyzing content for image opportunities' },
{ phase: 'PREP', label: 'Identifying image slots' },
{ phase: 'AI_CALL', label: 'Creating optimized prompts' },
{ phase: 'PARSE', label: 'Refining contextual image descriptions' },
{ phase: 'SAVE', label: 'Assigning prompts to image slots' },
];
}
@@ -286,210 +292,248 @@ export default function ProgressModal({
return match && match[1] ? match[1] : '';
};
// Helper to extract count from all step logs
const extractCountFromLogs = (pattern: RegExp): string => {
for (const log of allStepLogs) {
const match = log.message?.match(pattern);
if (match && match[1]) return match[1];
}
return '';
};
if (funcName.includes('cluster')) {
if (stepPhase === 'INIT') {
// For INIT: Try to extract keyword count from message or stepLogs
// Backend message might include keyword names (e.g., "Validating keyword1, keyword2, keyword3 and 5 more keywords")
// Or we need to extract the total count
if (message && message !== defaultLabel && message.includes('Validating')) {
// Try to extract total count from message
const countMatch = message.match(/(\d+)\s+more keyword/i);
if (countMatch) {
const moreCount = parseInt(countMatch[1], 10);
// Count keywords before "and X more" - typically 3
const shownCount = 3;
const totalCount = shownCount + moreCount;
return `Validating ${totalCount} keyword${totalCount !== 1 ? 's' : ''}`;
}
// Try to find total keyword count in any step log
for (const log of allStepLogs) {
const keywordCountMatch = log.message?.match(/(\d+)\s+keyword/i);
if (keywordCountMatch) {
const totalCount = parseInt(keywordCountMatch[1], 10);
return `Validating ${totalCount} keyword${totalCount !== 1 ? 's' : ''}`;
}
}
// If message has keyword names but no count, return as-is
return message;
}
// Fallback: try to extract count from stepLogs
for (const log of allStepLogs) {
const keywordCountMatch = log.message?.match(/(\d+)\s+keyword/i);
if (keywordCountMatch) {
const totalCount = parseInt(keywordCountMatch[1], 10);
return `Validating ${totalCount} keyword${totalCount !== 1 ? 's' : ''}`;
}
}
// Final fallback: use default label
return defaultLabel;
} else if (stepPhase === 'PREP') {
// For PREP: Show count of keywords being loaded
const keywordCount = extractCount(/(\d+)\s+keyword/i);
// For INIT: Try to extract keyword count
const keywordCount = extractCount(/(\d+)\s+keyword/i) || extractCountFromLogs(/(\d+)\s+keyword/i);
if (keywordCount) {
return `Loading ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} for clustering`;
return `Validating ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} for clustering`;
}
return message;
// Try to extract from "and X more keywords" format
const moreMatch = message.match(/(\d+)\s+more keyword/i);
if (moreMatch) {
const totalCount = parseInt(moreMatch[1], 10) + 3; // 3 shown + more
return `Validating ${totalCount} keywords for clustering`;
}
return 'Validating keywords for clustering';
} else if (stepPhase === 'PREP') {
// For PREP: Show "Analyzing keyword relationships"
return 'Analyzing keyword relationships';
} else if (stepPhase === 'AI_CALL') {
// For AI_CALL: Show "Generating clusters with Igny8 Semantic SEO Model"
return 'Generating clusters with Igny8 Semantic SEO Model';
// For AI_CALL: Try to get keyword count
const keywordCount = extractCount(/(\d+)\s+keyword/i) || extractCountFromLogs(/(\d+)\s+keyword/i);
if (keywordCount) {
return `Grouping keywords by search intent (${keywordCount} keywords)`;
}
return 'Grouping keywords by search intent';
} else if (stepPhase === 'PARSE') {
// For PARSE: Show count of clusters created
// For PARSE: Show "Organizing X semantic clusters"
const clusterCount = extractCount(/(\d+)\s+cluster/i) || extractCountFromLogs(/(\d+)\s+cluster/i);
if (clusterCount) {
return `Organizing ${clusterCount} semantic cluster${clusterCount !== '1' ? 's' : ''}`;
}
return 'Organizing semantic clusters';
} else if (stepPhase === 'SAVE') {
// For SAVE: Show "Saving X clusters with Y keywords"
const clusterCount = extractCount(/(\d+)\s+cluster/i) || extractCountFromLogs(/(\d+)\s+cluster/i);
const keywordCount = extractCountFromLogs(/(\d+)\s+keyword/i);
if (clusterCount && keywordCount) {
return `Saving ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} with ${keywordCount} keywords`;
} else if (clusterCount) {
return `Saving ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`;
}
return 'Saving clusters';
}
} else if (funcName.includes('idea')) {
if (stepPhase === 'INIT') {
// For INIT: Try to extract cluster count
const clusterCount = extractCount(/(\d+)\s+cluster/i);
if (clusterCount) {
return `${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} created`;
return `Analyzing ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} for content opportunities`;
}
// Try to find cluster count in any step log
for (const log of allStepLogs) {
const count = log.message?.match(/(\d+)\s+cluster/i);
if (count && count[1]) {
return `${count[1]} cluster${count[1] !== '1' ? 's' : ''} created`;
return `Analyzing ${count[1]} cluster${count[1] !== '1' ? 's' : ''} for content opportunities`;
}
}
return message;
} else if (stepPhase === 'SAVE') {
// For SAVE: Show count of clusters being saved
return 'Analyzing clusters for content opportunities';
} else if (stepPhase === 'PREP') {
// For PREP: Try to extract keyword count
const keywordCount = extractCount(/(\d+)\s+keyword/i);
if (keywordCount) {
return `Mapping ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} to topic briefs`;
}
return 'Mapping keywords to topic briefs';
} else if (stepPhase === 'AI_CALL') {
// For AI_CALL: Try to extract cluster count
const clusterCount = extractCount(/(\d+)\s+cluster/i);
if (clusterCount) {
return `Saving ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`;
return `Generating content ideas for ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`;
}
return message;
}
} else if (funcName.includes('idea')) {
if (stepPhase === 'INIT') {
// For INIT: Show "Verifying cluster integrity"
return 'Verifying cluster integrity';
} else if (stepPhase === 'PREP') {
// For PREP: Show "Loading cluster keywords"
return 'Loading cluster keywords';
} else if (stepPhase === 'AI_CALL') {
// For AI_CALL: Show "Generating ideas with Igny8 Semantic AI"
return 'Generating ideas with Igny8 Semantic AI';
// Try to find cluster count in any step log
for (const log of allStepLogs) {
const count = log.message?.match(/(\d+)\s+cluster/i);
if (count && count[1]) {
return `Generating content ideas for ${count[1]} cluster${count[1] !== '1' ? 's' : ''}`;
}
}
return 'Generating content ideas';
} else if (stepPhase === 'PARSE') {
// For PARSE: Show "X high-opportunity ideas generated"
// For PARSE: Show "Structuring X article outlines"
const ideaCount = extractCount(/(\d+)\s+idea/i);
if (ideaCount) {
return `${ideaCount} high-opportunity idea${ideaCount !== '1' ? 's' : ''} generated`;
return `Structuring ${ideaCount} article outline${ideaCount !== '1' ? 's' : ''}`;
}
// Try to find idea count in any step log
for (const log of allStepLogs) {
const count = log.message?.match(/(\d+)\s+idea/i);
if (count && count[1]) {
return `${count[1]} high-opportunity idea${count[1] !== '1' ? 's' : ''} generated`;
return `Structuring ${count[1]} article outline${count[1] !== '1' ? 's' : ''}`;
}
}
return message;
return 'Structuring article outlines';
} else if (stepPhase === 'SAVE') {
// For SAVE: Show "Content Outline for Ideas generated"
return 'Content Outline for Ideas generated';
// For SAVE: Show "Saving X content ideas with outlines"
const ideaCount = extractCount(/(\d+)\s+idea/i);
if (ideaCount) {
return `Saving ${ideaCount} content idea${ideaCount !== '1' ? 's' : ''} with outlines`;
}
// Try to find idea count in any step log
for (const log of allStepLogs) {
const count = log.message?.match(/(\d+)\s+idea/i);
if (count && count[1]) {
return `Saving ${count[1]} content idea${count[1] !== '1' ? 's' : ''} with outlines`;
}
}
return 'Saving content ideas with outlines';
}
} else if (funcName.includes('content')) {
if (stepPhase === 'AI_CALL') {
// For AI_CALL: Show "Writing article with Igny8 Semantic AI"
return 'Writing article with Igny8 Semantic AI';
} else if (stepPhase === 'PARSE') {
const articleCount = extractCount(/(\d+)\s+article/i);
if (articleCount) {
return `${articleCount} article${articleCount !== '1' ? 's' : ''} created`;
if (stepPhase === 'INIT') {
// Try to extract task/article count
const taskCount = extractCount(/(\d+)\s+task/i) || extractCount(/(\d+)\s+article/i);
if (taskCount) {
return `Preparing ${taskCount} article${taskCount !== '1' ? 's' : ''} for generation`;
}
return 'Preparing articles for generation';
} else if (stepPhase === 'PREP') {
// Try to extract keyword count
const keywordCount = extractCount(/(\d+)\s+keyword/i);
if (keywordCount) {
return `Building content brief with ${keywordCount} target keyword${keywordCount !== '1' ? 's' : ''}`;
}
return 'Building content brief with target keywords';
} else if (stepPhase === 'AI_CALL') {
// Try to extract count
const taskCount = extractCount(/(\d+)\s+task/i) || extractCount(/(\d+)\s+article/i);
if (taskCount) {
return `Writing ${taskCount} article${taskCount !== '1' ? 's' : ''} with Igny8 Semantic AI`;
}
return 'Writing articles with Igny8 Semantic AI';
} else if (stepPhase === 'PARSE') {
return 'Formatting HTML content and metadata';
} else if (stepPhase === 'SAVE') {
const articleCount = extractCount(/(\d+)\s+article/i);
const wordCount = extractCount(/(\d+[,\d]*)\s+word/i);
if (articleCount && wordCount) {
return `Saving ${articleCount} article${articleCount !== '1' ? 's' : ''} (${wordCount} words)`;
} else if (articleCount) {
return `Saving ${articleCount} article${articleCount !== '1' ? 's' : ''}`;
}
return 'Saving articles';
}
} else if (funcName.includes('image') && funcName.includes('from')) {
// Image generation from prompts
if (stepPhase === 'PREP') {
// Extract image count from PREP step message
const imageCount = extractCount(/(\d+)\s+image/i);
if (stepPhase === 'INIT') {
// Try to get image count
const imageCount = extractCount(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+image/i);
if (imageCount) {
return `Preparing to generate ${imageCount} image${imageCount !== '1' ? 's' : ''}`;
return `Queuing ${imageCount} image${imageCount !== '1' ? 's' : ''} for generation`;
}
if (stepLog?.message) {
const match = stepLog.message.match(/Preparing to generate (\d+)\s+image/i);
if (match && match[1]) {
return `Preparing to generate ${match[1]} image${match[1] !== '1' ? 's' : ''}`;
}
return 'Queuing images for generation';
} else if (stepPhase === 'PREP') {
// Extract image count from PREP step message
const imageCount = extractCount(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+image/i);
if (imageCount) {
return `Preparing AI image generation (${imageCount} images)`;
}
return 'Preparing image generation queue';
return 'Preparing AI image generation';
} else if (stepPhase === 'AI_CALL') {
// Extract current image number from message
const match = stepLog?.message?.match(/Generating.*image (\d+)/i);
if (match && match[1]) {
return `Generating image ${match[1]} with AI`;
// Extract current image number from message for "Generating image X/Y..."
const currentMatch = stepLog?.message?.match(/image (\d+)/i);
const totalCount = extractCountFromLogs(/(\d+)\s+image/i);
if (currentMatch && totalCount) {
return `Generating image ${currentMatch[1]}/${totalCount}...`;
} else if (currentMatch) {
return `Generating image ${currentMatch[1]}...`;
}
return 'Generating images with AI';
} else if (stepPhase === 'PARSE') {
// Extract image count from PARSE step
const imageCount = extractCount(/(\d+)\s+image/i);
const imageCount = extractCount(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+image/i);
if (imageCount) {
return `${imageCount} image${imageCount !== '1' ? 's' : ''} generated successfully`;
return `Processing ${imageCount} generated image${imageCount !== '1' ? 's' : ''}`;
}
if (stepLog?.message) {
const match = stepLog.message.match(/(\d+)\s+image.*generated/i);
if (match && match[1]) {
return `${match[1]} image${match[1] !== '1' ? 's' : ''} generated successfully`;
}
}
return 'Processing image URLs';
return 'Processing generated images';
} else if (stepPhase === 'SAVE') {
// Extract image count from SAVE step
const imageCount = extractCount(/(\d+)\s+image/i);
const imageCount = extractCount(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+image/i);
if (imageCount) {
return `Saving ${imageCount} image${imageCount !== '1' ? 's' : ''}`;
return `Uploading ${imageCount} image${imageCount !== '1' ? 's' : ''} to media library`;
}
if (stepLog?.message) {
const match = stepLog.message.match(/Saved image (\d+)/i);
if (match && match[1]) {
return `Saving image ${match[1]}`;
}
}
return 'Saving image URLs';
return 'Uploading images to media library';
}
} else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) {
// Image prompt generation
if (stepPhase === 'PREP') {
// Extract total image count from PREP step message
// Look for "Mapping Content for X Image Prompts"
const totalCount = extractCount(/(\d+)\s+Image Prompts/i) || extractCount(/(\d+)\s+image/i);
if (stepPhase === 'INIT') {
// Try to get image count
const imageCount = extractCount(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+image/i);
if (imageCount) {
return `Analyzing content for ${imageCount} image opportunit${imageCount !== '1' ? 'ies' : 'y'}`;
}
return 'Analyzing content for image opportunities';
} else if (stepPhase === 'PREP') {
// Extract total image count and calculate in-article count
const totalCount = extractCount(/(\d+)\s+Image Prompts/i) || extractCount(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+image/i);
if (totalCount) {
return `Mapping Content for ${totalCount} Image Prompts`;
}
// Try to extract from step log message
if (stepLog?.message) {
const match = stepLog.message.match(/Mapping Content for (\d+)\s+Image Prompts/i);
if (match && match[1]) {
return `Mapping Content for ${match[1]} Image Prompts`;
const total = parseInt(totalCount, 10);
const inArticleCount = total > 1 ? total - 1 : 0;
if (inArticleCount > 0) {
return `Identifying featured image and ${inArticleCount} in-article image slot${inArticleCount !== 1 ? 's' : ''}`;
}
return `Identifying featured image slot`;
}
return 'Mapping content for image prompts';
return 'Identifying image slots';
} else if (stepPhase === 'AI_CALL') {
// For AI_CALL: Show "Writing Featured Image Prompts"
return 'Writing Featured Image Prompts';
// For AI_CALL: Try to get count
const totalCount = extractCountFromLogs(/(\d+)\s+image/i) || extractCountFromLogs(/(\d+)\s+prompt/i);
if (totalCount) {
return `Creating optimized prompts for ${totalCount} image${totalCount !== '1' ? 's' : ''}`;
}
return 'Creating optimized prompts';
} else if (stepPhase === 'PARSE') {
// Extract in-article image count from PARSE step
// Look for "Writing X Inarticle Image Prompts"
const inArticleCount = extractCount(/(\d+)\s+Inarticle/i) || extractCount(/(\d+)\s+In-article/i);
const inArticleCount = extractCount(/(\d+)\s+In[-]article/i);
if (inArticleCount) {
return `Writing ${inArticleCount} Inarticle Image Prompts`;
return `Refining ${inArticleCount} contextual image description${inArticleCount !== '1' ? 's' : ''}`;
}
// Try to extract from step log message
if (stepLog?.message) {
const match = stepLog.message.match(/Writing (\d+)\s+In[-]article Image Prompts/i);
if (match && match[1]) {
return `Writing ${match[1]} Inarticle Image Prompts`;
// Fallback: calculate from total
const totalCount = extractCountFromLogs(/(\d+)\s+image/i);
if (totalCount) {
const total = parseInt(totalCount, 10);
const inArticle = total > 1 ? total - 1 : 0;
if (inArticle > 0) {
return `Refining ${inArticle} contextual image description${inArticle !== 1 ? 's' : ''}`;
}
}
return 'Writing Inarticle Image Prompts';
return 'Refining contextual image descriptions';
} else if (stepPhase === 'SAVE') {
// For SAVE: Extract prompt count from message
const promptCount = extractCount(/(\d+)\s+Prompts/i) || extractCount(/(\d+)\s+prompt/i);
const promptCount = extractCount(/(\d+)\s+Prompts/i) || extractCount(/(\d+)\s+prompt/i) || extractCountFromLogs(/(\d+)\s+prompt/i);
if (promptCount) {
return `Assigning ${promptCount} Prompts to Dedicated Slots`;
return `Assigning ${promptCount} prompt${promptCount !== '1' ? 's' : ''} to image slots`;
}
// Try to extract from step log message
if (stepLog?.message) {
const match = stepLog.message.match(/Assigning (\d+)\s+Prompts/i);
if (match && match[1]) {
return `Assigning ${match[1]} Prompts to Dedicated Slots`;
}
}
return 'Assigning Prompts to Dedicated Slots';
return 'Assigning prompts to image slots';
}
}

View File

@@ -1,9 +1,10 @@
/**
* NotificationDropdown - Dynamic notification dropdown using store
* Shows AI task completions, system events, and other notifications
* Fetches from API and syncs with local state
*/
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Dropdown } from "../ui/dropdown/Dropdown";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
@@ -70,11 +71,39 @@ export default function NotificationDropdown() {
const {
notifications,
unreadCount,
isLoading,
lastFetched,
markAsRead,
markAllAsRead,
removeNotification
removeNotification,
fetchNotifications: fetchFromAPI,
syncUnreadCount,
} = useNotificationStore();
// Fetch notifications on mount and periodically sync unread count
useEffect(() => {
// Initial fetch
fetchFromAPI();
// Sync unread count every 30 seconds
const interval = setInterval(() => {
syncUnreadCount();
}, 30000);
return () => clearInterval(interval);
}, [fetchFromAPI, syncUnreadCount]);
// Refetch when dropdown opens if data is stale (> 1 minute)
useEffect(() => {
if (isOpen && lastFetched) {
const staleThreshold = 60000; // 1 minute
const isStale = Date.now() - lastFetched.getTime() > staleThreshold;
if (isStale) {
fetchFromAPI();
}
}
}, [isOpen, lastFetched, fetchFromAPI]);
function toggleDropdown() {
setIsOpen(!isOpen);
}
@@ -177,7 +206,16 @@ export default function NotificationDropdown() {
{/* Notification List */}
<ul className="flex flex-col h-auto overflow-y-auto custom-scrollbar flex-1">
{notifications.length === 0 ? (
{isLoading && notifications.length === 0 ? (
<li className="flex flex-col items-center justify-center py-12 text-center">
<div className="w-12 h-12 mb-3 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center animate-pulse">
<BoltIcon className="w-6 h-6 text-gray-400" />
</div>
<p className="text-sm text-gray-500 dark:text-gray-400">
Loading notifications...
</p>
</li>
) : notifications.length === 0 ? (
<li className="flex flex-col items-center justify-center py-12 text-center">
<div className="w-12 h-12 mb-3 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
<BoltIcon className="w-6 h-6 text-gray-400" />