import React, { useState, useRef } from 'react'; import { useCopilotAction } from '@copilotkit/react-core'; import { blogWriterApi, BlogResearchRequest, BlogResearchResponse } from '../../services/blogWriterApi'; import { useResearchPolling } from '../../hooks/usePolling'; import ResearchProgressModal from './ResearchProgressModal'; import { researchCache } from '../../services/researchCache'; const useCopilotActionTyped = useCopilotAction as any; interface ResearchActionProps { onResearchComplete?: (research: BlogResearchResponse) => void; navigateToPhase?: (phase: string) => void; } export const ResearchAction: React.FC = ({ onResearchComplete, navigateToPhase }) => { const [currentTaskId, setCurrentTaskId] = useState(null); const [currentMessage, setCurrentMessage] = useState(''); const [showProgressModal, setShowProgressModal] = useState(false); const [forceUpdate, setForceUpdate] = useState(0); // Refs for form inputs (uncontrolled, avoids typing issues inside Copilot render) const keywordsRef = useRef(null); const blogLengthRef = useRef(null); // Track if we've navigated to research phase for this form display const hasNavigatedRef = useRef(false); const polling = useResearchPolling({ onProgress: (message) => { setCurrentMessage(message); setForceUpdate(prev => prev + 1); // Force re-render }, onComplete: (result) => { console.info('[ResearchAction] āœ… Research completed', { hasResult: !!result }); if (result && result.keywords) { researchCache.cacheResult( result.keywords, result.industry || 'General', result.target_audience || 'General', result ); } // Reset navigation tracking when research completes hasNavigatedRef.current = false; onResearchComplete?.(result); setCurrentTaskId(null); setCurrentMessage(''); setShowProgressModal(false); setForceUpdate(prev => prev + 1); }, onError: (error) => { console.error('Research polling error:', error); setCurrentTaskId(null); setCurrentMessage(''); setShowProgressModal(false); setForceUpdate(prev => prev + 1); } }); useCopilotActionTyped({ name: 'showResearchForm', description: 'Show keyword input form for blog research', parameters: [], handler: async () => { // Navigate to research phase when research form is shown // Reset navigation tracking so form render can navigate again if needed hasNavigatedRef.current = false; // Navigate immediately when handler is called if (navigateToPhase) { navigateToPhase('research'); } return { success: true, message: "šŸ” Let's Research Your Blog Topic\n\nWhat keywords and information would you like to use for your research? Please also specify the desired length of the blog post.\n\nKeywords or Topic *\ne.g., artificial intelligence, machine learning, AI trends\n\nBlog Length (words)\n\n1000 words (Medium blog)\n\nšŸš€ Start Research", showForm: true }; }, render: ({ status }: any) => { const _ = forceUpdate; // Navigate to research phase when form is rendered (if not already navigated and form is shown) // This ensures phase navigation updates when CopilotKit shows the research form // Only navigate when showing the form (not progress or completion states) const isShowingForm = polling.currentStatus !== 'completed' && polling.currentStatus !== 'in_progress' && polling.currentStatus !== 'running'; if (isShowingForm && !hasNavigatedRef.current && navigateToPhase) { // Use setTimeout to avoid calling during render setTimeout(() => { if (!hasNavigatedRef.current) { navigateToPhase('research'); hasNavigatedRef.current = true; } }, 0); } if (polling.currentStatus === 'completed' && polling.progressMessages.length > 0) { const latestMessage = polling.progressMessages[polling.progressMessages.length - 1]; return (

āœ… Research completed successfully!

{latestMessage?.message || 'Research data is now available for your blog.'}

); } if (polling.currentStatus === 'in_progress' || polling.currentStatus === 'running') { return (

šŸ”„ Research in progress...

{currentMessage || 'Gathering research data...'}

); } return (

šŸ” Let's Research Your Blog Topic

What keywords and information would you like to use for your research? Please also specify the desired length of the blog post.

); } }); // Additional action to catch the specific suggestion message useCopilotActionTyped({ name: 'researchTopic', description: 'Research topic with keywords and persona context using Google Search grounding', parameters: [ { name: 'keywords', type: 'string', description: 'Comma-separated keywords or topic description', required: false }, { name: 'industry', type: 'string', description: 'Industry', required: false }, { name: 'target_audience', type: 'string', description: 'Target audience', required: false }, { name: 'blogLength', type: 'string', description: 'Target blog length in words', required: false } ], handler: async ({ keywords = '', industry = 'General', target_audience = 'General', blogLength = '1000' }: any) => { try { const trimmed = keywords.trim(); if (!trimmed) { return "Please provide keywords or a topic for research."; } const keywordList = trimmed.includes(',') ? trimmed.split(',').map((k: string) => k.trim()).filter(Boolean) : [trimmed]; // Navigate to research phase when research starts navigateToPhase?.('research'); const payload: BlogResearchRequest = { keywords: keywordList, industry, target_audience, word_count_target: parseInt(blogLength) }; const { task_id } = await blogWriterApi.startResearch(payload); setCurrentTaskId(task_id); setShowProgressModal(true); polling.startPolling(task_id); return "Starting research with your provided keywords."; } catch (error) { console.error('Failed to start research:', error); return "Failed to start research. Please try again."; } } }); return ( <> {showProgressModal && ( setShowProgressModal(false)} /> )} ); }; export default ResearchAction;