diff --git a/frontend/src/components/common/ProgressModal.tsx b/frontend/src/components/common/ProgressModal.tsx index 0670f451..37396976 100644 --- a/frontend/src/components/common/ProgressModal.tsx +++ b/frontend/src/components/common/ProgressModal.tsx @@ -46,13 +46,13 @@ const getSuccessMessage = (functionId?: string, title?: string, stepLogs?: any[] const clusterCount = extractCount(/(\d+)\s+cluster/i, stepLogs || []); if (keywordCount && clusterCount) { - return `Clustering complete\n${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} created from ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''}`; + return `Clustering complete\n${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} mapped and grouped into ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`; } else if (clusterCount) { return `Clustering complete\n${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} created`; } else if (keywordCount) { - return `Clustering complete\nProcessed ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''}`; + return `Clustering complete\n${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} mapped and grouped into clusters`; } - return 'Clustering complete\nKeywords grouped into meaningful clusters'; + return 'Clustering complete\nKeywords mapped and grouped into clusters'; } if (funcName.includes('idea')) { const clusterCount = extractCount(/(\d+)\s+cluster/i, stepLogs || []); @@ -462,7 +462,37 @@ export default function ProgressModal({ {/* Header */}

- {title} + {(() => { + const funcName = (functionId || title || '').toLowerCase(); + if (funcName.includes('cluster')) { + // Try to extract keyword count from INIT step + const initStepLog = stepLogs.find(log => log.stepName === 'INIT'); + if (initStepLog?.message) { + const message = initStepLog.message; + // Try to extract from "Validating keyword1, keyword2, keyword3 and 5 more keywords" + const countMatch = message.match(/(\d+)\s+more keyword/i); + if (countMatch) { + const moreCount = parseInt(countMatch[1], 10); + const shownCount = 3; // Typically shows 3 keywords + const totalCount = shownCount + moreCount; + return `Mapping ${totalCount} Keywords into Keyword Clusters`; + } + // Try to extract from "Validating X keywords" + const simpleMatch = message.match(/(\d+)\s+keyword/i); + if (simpleMatch) { + return `Mapping ${simpleMatch[1]} Keywords into Keyword Clusters`; + } + } + // Try to find keyword count in any step log + for (const log of stepLogs) { + const keywordCountMatch = log.message?.match(/(\d+)\s+keyword/i); + if (keywordCountMatch) { + return `Mapping ${keywordCountMatch[1]} Keywords into Keyword Clusters`; + } + } + } + return title; + })()}

{!showSuccess && status !== 'completed' && (

{message}

diff --git a/frontend/src/hooks/useResourceDebug.ts b/frontend/src/hooks/useResourceDebug.ts new file mode 100644 index 00000000..b74c7d06 --- /dev/null +++ b/frontend/src/hooks/useResourceDebug.ts @@ -0,0 +1,30 @@ +import { useState, useEffect } from 'react'; + +/** + * Hook to check if Resource Debug is enabled + * This controls both Resource Debug overlay and AI Function Logs + */ +export function useResourceDebug(): boolean { + const [enabled, setEnabled] = useState(false); + + useEffect(() => { + // Load initial state + const saved = localStorage.getItem('debug_resource_tracking_enabled'); + setEnabled(saved === 'true'); + + // Listen for toggle changes + const handleToggle = (e: Event) => { + const customEvent = e as CustomEvent; + setEnabled(customEvent.detail); + }; + + window.addEventListener('debug-resource-tracking-toggle', handleToggle); + + return () => { + window.removeEventListener('debug-resource-tracking-toggle', handleToggle); + }; + }, []); + + return enabled; +} + diff --git a/frontend/src/pages/Planner/Keywords.tsx b/frontend/src/pages/Planner/Keywords.tsx index e48cf3ab..4ab764a9 100644 --- a/frontend/src/pages/Planner/Keywords.tsx +++ b/frontend/src/pages/Planner/Keywords.tsx @@ -30,6 +30,7 @@ import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/di import FormModal from '../../components/common/FormModal'; import ProgressModal from '../../components/common/ProgressModal'; import { useProgressModal } from '../../hooks/useProgressModal'; +import { useResourceDebug } from '../../hooks/useResourceDebug'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { ArrowUpIcon, PlusIcon, ListIcon, DownloadIcon } from '../../icons'; import { useKeywordsImportExport } from '../../config/import-export.config'; @@ -89,6 +90,9 @@ export default function Keywords() { const progressModal = useProgressModal(); const hasReloadedRef = useRef(false); + // Resource Debug toggle - controls AI Function Logs + const resourceDebugEnabled = useResourceDebug(); + // AI Function Logs state const [aiLogs, setAiLogs] = useState(null); const lastLoggedPercentageRef = useRef(-1); + // Helper function to add log entry (only if Resource Debug is enabled) + const addAiLog = useCallback((log: { + timestamp: string; + type: 'request' | 'success' | 'error' | 'step'; + action: string; + data: any; + stepName?: string; + percentage?: number; + }) => { + if (resourceDebugEnabled) { + setAiLogs(prev => [...prev, log]); + } + }, [resourceDebugEnabled]); + // Load sectors for active site using sector store useEffect(() => { if (activeSite) { @@ -358,13 +376,13 @@ export default function Keywords() { sector_id: sectorId, }; - // Log request - setAiLogs(prev => [...prev, { + // Log request (only if Resource Debug is enabled) + addAiLog({ timestamp: new Date().toISOString(), type: 'request', action: 'auto_cluster (Bulk Action)', data: requestData, - }]); + }); try { const result = await autoClusterKeywords(numIds, sectorId); @@ -374,12 +392,12 @@ export default function Keywords() { // Error response from API const errorMsg = result.error || 'Failed to cluster keywords'; // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'auto_cluster (Bulk Action)', data: { error: errorMsg, keyword_count: numIds.length }, - }]); + }); toast.error(errorMsg); return; } @@ -387,19 +405,19 @@ export default function Keywords() { if (result && result.success) { if (result.task_id) { // Log success with task_id - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'success', action: 'auto_cluster (Bulk Action)', data: { task_id: result.task_id, message: result.message, keyword_count: numIds.length }, - }]); + }); // Async task - open progress modal hasReloadedRef.current = false; progressModal.openModal(result.task_id, 'Auto-Clustering Keywords', 'ai-auto-cluster-01'); // Don't show toast - progress modal will show status } else { // Log success with results - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'success', action: 'auto_cluster (Bulk Action)', @@ -409,7 +427,7 @@ export default function Keywords() { keyword_count: numIds.length, message: result.message, }, - }]); + }); // Synchronous completion toast.success(`Clustering complete: ${result.clusters_created || 0} clusters created, ${result.keywords_updated || 0} keywords updated`); if (!hasReloadedRef.current) { @@ -421,12 +439,12 @@ export default function Keywords() { // Unexpected response format - show error const errorMsg = result?.error || 'Unexpected response format'; // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'auto_cluster (Bulk Action)', data: { error: errorMsg, keyword_count: numIds.length }, - }]); + }); toast.error(errorMsg); } } catch (error: any) { @@ -440,12 +458,12 @@ export default function Keywords() { } } // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'auto_cluster (Bulk Action)', data: { error: errorMsg, keyword_count: numIds.length }, - }]); + }); toast.error(errorMsg); } } else { @@ -470,7 +488,7 @@ export default function Keywords() { const stepType = currentStatus === 'error' ? 'error' : currentStatus === 'completed' ? 'success' : 'step'; - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: stepType, action: progressModal.title || 'AI Function', @@ -483,7 +501,7 @@ export default function Keywords() { status: currentStatus, details: progress.details, }, - }]); + }); lastLoggedStepRef.current = currentStep; lastLoggedPercentageRef.current = currentPercentage; @@ -493,7 +511,7 @@ export default function Keywords() { const stepType = currentStatus === 'error' ? 'error' : currentStatus === 'completed' ? 'success' : 'step'; - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: stepType, action: progressModal.title || 'AI Function', @@ -506,7 +524,7 @@ export default function Keywords() { status: currentStatus, details: progress.details, }, - }]); + }); lastLoggedPercentageRef.current = currentPercentage; } @@ -518,7 +536,7 @@ export default function Keywords() { (currentStatus === 'completed' && lastLoggedStepRef.current !== 'completed')) { const stepType = currentStatus === 'error' ? 'error' : 'success'; - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: stepType, action: progressModal.title || 'AI Function', @@ -531,12 +549,12 @@ export default function Keywords() { status: currentStatus, details: progress.details, }, - }]); + }); lastLoggedStepRef.current = currentStep || currentStatus; } } - }, [progressModal.progress, progressModal.taskId, progressModal.isOpen, progressModal.title]); + }, [progressModal.progress, progressModal.taskId, progressModal.isOpen, progressModal.title, addAiLog]); // Reset step tracking when modal closes or opens useEffect(() => { @@ -888,8 +906,8 @@ export default function Keywords() { }} /> - {/* AI Function Logs - Display below table */} - {aiLogs.length > 0 && ( + {/* AI Function Logs - Display below table (only when Resource Debug is enabled) */} + {resourceDebugEnabled && aiLogs.length > 0 && (

diff --git a/frontend/src/pages/Writer/Tasks.tsx b/frontend/src/pages/Writer/Tasks.tsx index d4e77f32..9ade2909 100644 --- a/frontend/src/pages/Writer/Tasks.tsx +++ b/frontend/src/pages/Writer/Tasks.tsx @@ -23,6 +23,7 @@ import { import FormModal from '../../components/common/FormModal'; import ProgressModal from '../../components/common/ProgressModal'; import { useProgressModal } from '../../hooks/useProgressModal'; +import { useResourceDebug } from '../../hooks/useResourceDebug'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { TaskIcon, PlusIcon, DownloadIcon } from '../../icons'; import { createTasksPageConfig } from '../../config/pages/tasks.config'; @@ -85,11 +86,28 @@ export default function Tasks() { percentage?: number; }>>([]); + // Resource Debug toggle - controls AI Function Logs + const resourceDebugEnabled = useResourceDebug(); + // Track last logged step to avoid duplicates const lastLoggedStepRef = useRef(null); const lastLoggedPercentageRef = useRef(-1); const hasReloadedRef = useRef(false); + // Helper function to add log entry (only if Resource Debug is enabled) + const addAiLog = useCallback((log: { + timestamp: string; + type: 'request' | 'success' | 'error' | 'step'; + action: string; + data: any; + stepName?: string; + percentage?: number; + }) => { + if (resourceDebugEnabled) { + setAiLogs(prev => [...prev, log]); + } + }, [resourceDebugEnabled]); + // Load clusters for filter dropdown useEffect(() => { const loadClusters = async () => { @@ -230,12 +248,12 @@ export default function Tasks() { }; // Log request - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'request', action: 'generate_content (Row Action)', data: requestData, - }]); + }); try { const result = await autoGenerateContent([row.id]); @@ -243,7 +261,7 @@ export default function Tasks() { if (result.success) { if (result.task_id) { // Log success with task_id - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'success', action: 'generate_content (Row Action)', @@ -254,7 +272,7 @@ export default function Tasks() { toast.success('Content generation started'); } else { // Log success with results - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'success', action: 'generate_content (Row Action)', @@ -266,7 +284,7 @@ export default function Tasks() { } } else { // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'generate_content (Row Action)', @@ -276,7 +294,7 @@ export default function Tasks() { } } catch (error: any) { // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'generate_content (Row Action)', @@ -308,7 +326,7 @@ export default function Tasks() { }; // Log request - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'request', action: 'generate_images (Bulk Action)', @@ -320,7 +338,7 @@ export default function Tasks() { if (result.success) { if (result.task_id) { // Log success with task_id - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'success', action: 'generate_images (Bulk Action)', @@ -331,7 +349,7 @@ export default function Tasks() { toast.success('Image generation started'); } else { // Log success with results - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'success', action: 'generate_images (Bulk Action)', @@ -343,7 +361,7 @@ export default function Tasks() { } } else { // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'generate_images (Bulk Action)', @@ -353,7 +371,7 @@ export default function Tasks() { } } catch (error: any) { // Log error - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: 'error', action: 'generate_images (Bulk Action)', @@ -383,7 +401,7 @@ export default function Tasks() { const stepType = currentStatus === 'error' ? 'error' : currentStatus === 'completed' ? 'success' : 'step'; - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: stepType, action: progressModal.title || 'AI Function', @@ -396,7 +414,7 @@ export default function Tasks() { status: currentStatus, details: progress.details, }, - }]); + }); lastLoggedStepRef.current = currentStep; lastLoggedPercentageRef.current = currentPercentage; @@ -406,7 +424,7 @@ export default function Tasks() { const stepType = currentStatus === 'error' ? 'error' : currentStatus === 'completed' ? 'success' : 'step'; - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: stepType, action: progressModal.title || 'AI Function', @@ -419,7 +437,7 @@ export default function Tasks() { status: currentStatus, details: progress.details, }, - }]); + }); lastLoggedPercentageRef.current = currentPercentage; } @@ -431,7 +449,7 @@ export default function Tasks() { (currentStatus === 'completed' && lastLoggedStepRef.current !== 'completed')) { const stepType = currentStatus === 'error' ? 'error' : 'success'; - setAiLogs(prev => [...prev, { + addAiLog({ timestamp: new Date().toISOString(), type: stepType, action: progressModal.title || 'AI Function', @@ -444,12 +462,12 @@ export default function Tasks() { status: currentStatus, details: progress.details, }, - }]); + }); lastLoggedStepRef.current = currentStep || currentStatus; } } - }, [progressModal.progress, progressModal.taskId, progressModal.isOpen, progressModal.title]); + }, [progressModal.progress, progressModal.taskId, progressModal.isOpen, progressModal.title, addAiLog]); // Reset step tracking when modal closes or opens useEffect(() => { @@ -657,8 +675,8 @@ export default function Tasks() { }} /> - {/* AI Function Logs - Display below table */} - {aiLogs.length > 0 && ( + {/* AI Function Logs - Display below table (only when Resource Debug is enabled) */} + {resourceDebugEnabled && aiLogs.length > 0 && (