AI Analysis and Content Strategy fixes. Enhanced Strategy Routes refactoring.
This commit is contained in:
@@ -275,6 +275,12 @@ export const BlogWriter: React.FC = () => {
|
||||
|
||||
const handlePhaseClick = useCallback((phaseId: string) => {
|
||||
navigateToPhase(phaseId);
|
||||
// When clicking Research phase, ensure we navigate to research phase (this will trigger research form to show)
|
||||
if (phaseId === 'research' && !research) {
|
||||
debug.log('[BlogWriter] Research phase clicked - navigating to research phase to show form');
|
||||
// navigateToPhase already called above, which will set currentPhase to 'research'
|
||||
// BlogWriterLandingSection will detect currentPhase === 'research' and show ManualResearchForm
|
||||
}
|
||||
if (phaseId === 'seo') {
|
||||
if (seoAnalysis) {
|
||||
setIsSEOAnalysisModalOpen(true);
|
||||
@@ -283,7 +289,7 @@ export const BlogWriter: React.FC = () => {
|
||||
runSEOAnalysisDirect();
|
||||
}
|
||||
}
|
||||
}, [navigateToPhase, seoAnalysis, runSEOAnalysisDirect, setIsSEOAnalysisModalOpen]);
|
||||
}, [navigateToPhase, seoAnalysis, research, runSEOAnalysisDirect, setIsSEOAnalysisModalOpen]);
|
||||
|
||||
const outlineGenRef = useRef<any>(null);
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Blog Writer Cost Alerts Integration
|
||||
*
|
||||
* Example integration of Priority 2 alerts (cost estimation, trends, OSS recommendations)
|
||||
* into the Blog Writer component.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Alert, AlertTitle, Button, Collapse } from '@mui/material';
|
||||
import { usePriority2Alerts, useCostEstimationAlert } from '../../../hooks/usePriority2Alerts';
|
||||
import Priority2AlertBanner from '../../shared/Priority2AlertBanner';
|
||||
import { useSubscription } from '../../../contexts/SubscriptionContext';
|
||||
import { checkPreflight, PreflightOperation } from '../../../services/billingService';
|
||||
import { showToastNotification } from '../../../utils/toastNotifications';
|
||||
|
||||
interface BlogWriterCostAlertsProps {
|
||||
userId?: string;
|
||||
onResearchStart?: () => void;
|
||||
onOutlineStart?: () => void;
|
||||
onContentStart?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blog Writer Cost Alerts Component
|
||||
*
|
||||
* Displays Priority 2 alerts and provides cost estimation before operations.
|
||||
* Integrates with Blog Writer's research, outline, and content generation workflows.
|
||||
*/
|
||||
export const BlogWriterCostAlerts: React.FC<BlogWriterCostAlertsProps> = ({
|
||||
userId,
|
||||
onResearchStart,
|
||||
onOutlineStart,
|
||||
onContentStart,
|
||||
}) => {
|
||||
const { subscription } = useSubscription();
|
||||
const { alerts, refreshAlerts, dismissAlert } = usePriority2Alerts({
|
||||
userId,
|
||||
enabled: !!userId && subscription?.active,
|
||||
checkInterval: 120000, // Check every 2 minutes
|
||||
});
|
||||
|
||||
const { showEstimationAlert } = useCostEstimationAlert();
|
||||
|
||||
// Estimate cost for blog generation workflow
|
||||
const estimateBlogWorkflowCost = async (workflowType: 'research' | 'outline' | 'content') => {
|
||||
if (!userId) return;
|
||||
|
||||
try {
|
||||
const operations: PreflightOperation[] = [];
|
||||
|
||||
if (workflowType === 'research') {
|
||||
// Research typically involves: 3-5 Exa searches + 1 LLM call for analysis
|
||||
operations.push(
|
||||
{
|
||||
provider: 'exa',
|
||||
operation_type: 'research',
|
||||
tokens_requested: 0,
|
||||
},
|
||||
{
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'research',
|
||||
tokens_requested: 2000, // Estimated tokens for research analysis
|
||||
}
|
||||
);
|
||||
} else if (workflowType === 'outline') {
|
||||
// Outline generation: 1 LLM call
|
||||
operations.push({
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'outline_generation',
|
||||
tokens_requested: 1500, // Estimated tokens for outline
|
||||
});
|
||||
} else if (workflowType === 'content') {
|
||||
// Content generation: 2-3 LLM calls (one per section typically)
|
||||
operations.push(
|
||||
{
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'content_generation',
|
||||
tokens_requested: 3000, // Estimated tokens per section
|
||||
},
|
||||
{
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'content_generation',
|
||||
tokens_requested: 3000,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const preflightResult = await checkPreflight(operations[0]); // Check first operation
|
||||
const estimatedCost = preflightResult.estimated_cost || 0;
|
||||
|
||||
if (estimatedCost > 0.01) {
|
||||
showEstimationAlert(
|
||||
estimatedCost,
|
||||
`${workflowType} generation`,
|
||||
() => {
|
||||
// User confirmed - proceed with operation
|
||||
if (workflowType === 'research' && onResearchStart) {
|
||||
onResearchStart();
|
||||
} else if (workflowType === 'outline' && onOutlineStart) {
|
||||
onOutlineStart();
|
||||
} else if (workflowType === 'content' && onContentStart) {
|
||||
onContentStart();
|
||||
}
|
||||
},
|
||||
() => {
|
||||
showToastNotification('Operation cancelled', 'info');
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[BlogWriterCostAlerts] Error estimating cost:', error);
|
||||
// Don't block operation on estimation failure
|
||||
}
|
||||
};
|
||||
|
||||
// Filter alerts relevant to Blog Writer
|
||||
const blogWriterAlerts = alerts.filter(alert =>
|
||||
alert.type === 'cost_trend' ||
|
||||
alert.type === 'oss_recommendation' ||
|
||||
(alert.type === 'cost_estimation' && alert.message.includes('blog'))
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
{/* Priority 2 Alert Banner */}
|
||||
{blogWriterAlerts.length > 0 && (
|
||||
<Priority2AlertBanner
|
||||
alerts={blogWriterAlerts}
|
||||
onDismiss={dismissAlert}
|
||||
maxAlerts={2}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Cost Estimation Info Alert */}
|
||||
<Collapse in={blogWriterAlerts.length === 0}>
|
||||
<Alert
|
||||
severity="info"
|
||||
icon={<></>}
|
||||
sx={{
|
||||
mb: 2,
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
border: '1px solid rgba(59, 130, 246, 0.2)',
|
||||
}}
|
||||
>
|
||||
<AlertTitle sx={{ fontWeight: 'bold', mb: 0.5 }}>
|
||||
💡 Cost Transparency
|
||||
</AlertTitle>
|
||||
<Box sx={{ fontSize: '0.875rem' }}>
|
||||
Blog generation typically costs:
|
||||
<ul style={{ margin: '8px 0', paddingLeft: '20px' }}>
|
||||
<li><strong>Research</strong>: ~$0.01-0.02 (3-5 searches + analysis)</li>
|
||||
<li><strong>Outline</strong>: ~$0.005-0.01 (1 LLM call)</li>
|
||||
<li><strong>Content</strong>: ~$0.01-0.03 (2-3 LLM calls per section)</li>
|
||||
</ul>
|
||||
<strong>Total per blog</strong>: ~$0.03-0.06 (using OSS models)
|
||||
</Box>
|
||||
</Alert>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for Blog Writer cost estimation
|
||||
* Use this in Blog Writer components before triggering operations
|
||||
*/
|
||||
export const useBlogWriterCostEstimation = () => {
|
||||
const { showEstimationAlert } = useCostEstimationAlert();
|
||||
|
||||
const estimateAndProceed = async (
|
||||
workflowType: 'research' | 'outline' | 'content',
|
||||
onProceed: () => void,
|
||||
userId?: string
|
||||
) => {
|
||||
if (!userId) {
|
||||
// No user ID - proceed without estimation
|
||||
onProceed();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const operations: PreflightOperation[] = [];
|
||||
|
||||
// Define operations based on workflow type
|
||||
if (workflowType === 'research') {
|
||||
operations.push(
|
||||
{ provider: 'exa', operation_type: 'research', tokens_requested: 0 },
|
||||
{
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'research',
|
||||
tokens_requested: 2000
|
||||
}
|
||||
);
|
||||
} else if (workflowType === 'outline') {
|
||||
operations.push({
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'outline_generation',
|
||||
tokens_requested: 1500,
|
||||
});
|
||||
} else if (workflowType === 'content') {
|
||||
operations.push(
|
||||
{
|
||||
provider: 'gemini',
|
||||
model: 'gemini-2.5-flash',
|
||||
operation_type: 'content_generation',
|
||||
tokens_requested: 3000,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (operations.length > 0) {
|
||||
const preflightResult = await checkPreflight(operations[0]);
|
||||
const estimatedCost = preflightResult.estimated_cost || 0;
|
||||
|
||||
if (estimatedCost > 0.01) {
|
||||
showEstimationAlert(
|
||||
estimatedCost,
|
||||
`${workflowType} generation`,
|
||||
onProceed,
|
||||
() => showToastNotification('Operation cancelled', 'info')
|
||||
);
|
||||
} else {
|
||||
// Low cost - proceed directly
|
||||
onProceed();
|
||||
}
|
||||
} else {
|
||||
onProceed();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[BlogWriterCostEstimation] Error:', error);
|
||||
// On error, proceed anyway (don't block user)
|
||||
onProceed();
|
||||
}
|
||||
};
|
||||
|
||||
return { estimateAndProceed };
|
||||
};
|
||||
|
||||
export default BlogWriterCostAlerts;
|
||||
@@ -20,24 +20,24 @@ export const BlogWriterLandingSection: React.FC<BlogWriterLandingSectionProps> =
|
||||
// Only show landing/initial content when no research exists
|
||||
// Phase navigation header is always visible, so this is just the initial content
|
||||
if (!research) {
|
||||
// Show research form only when user explicitly navigated to research phase (clicked "Start Research")
|
||||
if (currentPhase === 'research') {
|
||||
return <ManualResearchForm onResearchComplete={onResearchComplete} />;
|
||||
}
|
||||
|
||||
// Default: Always show landing page when no research exists
|
||||
// This ensures landing page is shown on initial load
|
||||
return (
|
||||
<>
|
||||
{/* Show manual research form when on research phase and CopilotKit unavailable */}
|
||||
{!copilotKitAvailable && currentPhase === 'research' && (
|
||||
<ManualResearchForm onResearchComplete={onResearchComplete} />
|
||||
)}
|
||||
{/* Show landing page for CopilotKit flow or when not on research phase */}
|
||||
{(!copilotKitAvailable && currentPhase !== 'research') || copilotKitAvailable ? (
|
||||
<BlogWriterLanding
|
||||
onStartWriting={() => {
|
||||
// Navigate to research phase to start the workflow
|
||||
navigateToPhase('research');
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
<BlogWriterLanding
|
||||
onStartWriting={() => {
|
||||
// Navigate to research phase to show the research form
|
||||
navigateToPhase('research');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// If research exists, don't show landing section (phase content will be shown instead)
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
useResearchPolling,
|
||||
useBlogWriterResearchPolling,
|
||||
useOutlinePolling,
|
||||
useMediumGenerationPolling,
|
||||
useRewritePolling,
|
||||
@@ -24,8 +24,8 @@ export const useBlogWriterPolling = ({
|
||||
onContentConfirmed,
|
||||
navigateToPhase,
|
||||
}: UseBlogWriterPollingProps) => {
|
||||
// Research polling hook (for context awareness)
|
||||
const researchPolling = useResearchPolling({
|
||||
// Research polling hook (for context awareness) - uses blog writer endpoint
|
||||
const researchPolling = useBlogWriterResearchPolling({
|
||||
onComplete: onResearchComplete,
|
||||
onError: (error) => console.error('Research polling error:', error)
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { blogWriterApi, BlogResearchRequest, BlogResearchResponse } from '../../services/blogWriterApi';
|
||||
import { useResearchPolling } from '../../hooks/usePolling';
|
||||
import { useBlogWriterResearchPolling } from '../../hooks/usePolling';
|
||||
import ResearchProgressModal from './ResearchProgressModal';
|
||||
import { researchCache } from '../../services/researchCache';
|
||||
|
||||
@@ -22,7 +22,7 @@ export const ManualResearchForm: React.FC<ManualResearchFormProps> = ({ onResear
|
||||
const keywordsRef = useRef<HTMLInputElement | null>(null);
|
||||
const blogLengthRef = useRef<HTMLSelectElement | null>(null);
|
||||
|
||||
const polling = useResearchPolling({
|
||||
const polling = useBlogWriterResearchPolling({
|
||||
onProgress: (message) => {
|
||||
setCurrentMessage(message);
|
||||
},
|
||||
|
||||
@@ -66,6 +66,9 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
|
||||
switch (phaseId) {
|
||||
case 'research':
|
||||
// Always show "Start Research" button when on research phase and no research exists yet
|
||||
// This allows users to manually trigger research form
|
||||
// If research already exists, don't show the button (user can click the phase button to view)
|
||||
if (!hasResearch) {
|
||||
return { label: 'Start Research', handler: actionHandlers.onResearchAction || null };
|
||||
}
|
||||
@@ -326,10 +329,10 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
// 2. Action handler exists
|
||||
// 3. Phase is not disabled
|
||||
// 4. Show for current phase OR next actionable phase (not completed) OR phases with available actions
|
||||
// For research phase: always show if no research exists
|
||||
// For research phase: always show button when on research phase (allows manual trigger)
|
||||
// For outline phase: always show if research exists but no outline (like research phase)
|
||||
// For SEO phase: always show if action handler exists (prerequisites are met)
|
||||
const isResearchPhase = phase.id === 'research' && !hasResearch;
|
||||
const isResearchPhase = phase.id === 'research' && action.handler; // Always show if handler exists
|
||||
// Outline phase: show action whenever research exists and action handler is available
|
||||
// This allows users to create/regenerate outline after research, even if cached one exists
|
||||
const isOutlinePhase = phase.id === 'outline' && hasResearch && action.handler;
|
||||
@@ -368,12 +371,12 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
// This is critical because SEO prerequisites (hasContent && contentConfirmed) are validated in getActionForPhase,
|
||||
// so if action.handler exists, we should show it regardless of phase navigation's disabled state
|
||||
// DUAL MODE: Show action buttons even when CopilotKit is available (users can use either method)
|
||||
// For research phase: show action button when on research phase and no research exists yet (to start research)
|
||||
const showAction = action.handler && (
|
||||
isCurrent ||
|
||||
(isCurrent && phase.id === 'research' && !hasResearch) || // Show "Start Research" when on research phase with no research
|
||||
(isCurrent && phase.id !== 'research') || // For other phases, show action when current
|
||||
(!isCompleted && !isDisabled) ||
|
||||
isResearchPhase ||
|
||||
isOutlinePhase ||
|
||||
isSEOPhase // Show SEO actions when handler exists - handler existence means prerequisites are met, so ignore isDisabled
|
||||
(phase.id !== 'research' && (isResearchPhase || isOutlinePhase || isSEOPhase)) // Show for outline/SEO when appropriate
|
||||
);
|
||||
|
||||
// Determine chip class
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useCopilotAction } from '@copilotkit/react-core';
|
||||
import { blogWriterApi, BlogResearchRequest, BlogResearchResponse } from '../../services/blogWriterApi';
|
||||
import { useResearchPolling } from '../../hooks/usePolling';
|
||||
import { useBlogWriterResearchPolling } from '../../hooks/usePolling';
|
||||
import ResearchProgressModal from './ResearchProgressModal';
|
||||
import { researchCache } from '../../services/researchCache';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ResearchAction: React.FC<ResearchActionProps> = ({ onResearchComple
|
||||
// Track if we've navigated to research phase for this form display
|
||||
const hasNavigatedRef = useRef<boolean>(false);
|
||||
|
||||
const polling = useResearchPolling({
|
||||
const polling = useBlogWriterResearchPolling({
|
||||
onProgress: (message) => {
|
||||
setCurrentMessage(message);
|
||||
setForceUpdate(prev => prev + 1); // Force re-render
|
||||
@@ -128,40 +128,64 @@ export const ResearchAction: React.FC<ResearchActionProps> = ({ onResearchComple
|
||||
};
|
||||
},
|
||||
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;
|
||||
try {
|
||||
const _ = forceUpdate;
|
||||
|
||||
// Safely access polling state with defaults - handle case where polling might not be initialized
|
||||
let currentStatus = 'idle';
|
||||
let progressMessages: Array<{ timestamp: string; message: string }> = [];
|
||||
|
||||
try {
|
||||
if (polling) {
|
||||
currentStatus = polling.currentStatus || 'idle';
|
||||
progressMessages = polling.progressMessages || [];
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (polling.currentStatus === 'completed' && polling.progressMessages.length > 0) {
|
||||
const latestMessage = polling.progressMessages[polling.progressMessages.length - 1];
|
||||
} catch (pollingError) {
|
||||
console.warn('[ResearchAction] Error accessing polling state in render:', pollingError);
|
||||
// Use defaults already set above
|
||||
}
|
||||
|
||||
// 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 = currentStatus !== 'completed' &&
|
||||
currentStatus !== 'in_progress' &&
|
||||
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 (currentStatus === 'completed' && progressMessages.length > 0) {
|
||||
const latestMessage = progressMessages[progressMessages.length - 1];
|
||||
return (
|
||||
<div style={{ padding: '16px', backgroundColor: '#e8f5e8', borderRadius: '8px', border: '1px solid #4caf50', margin: '8px 0' }}>
|
||||
<p style={{ margin: 0, color: '#4caf50', fontWeight: '500' }}>✅ Research completed successfully!</p>
|
||||
<p style={{ margin: '8px 0 0 0', color: '#666', fontSize: '14px' }}>{latestMessage?.message || 'Research data is now available for your blog.'}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentStatus === 'in_progress' || currentStatus === 'running') {
|
||||
return (
|
||||
<div style={{ padding: '16px', backgroundColor: '#fff3e0', borderRadius: '8px', border: '1px solid #ff9800', margin: '8px 0' }}>
|
||||
<p style={{ margin: 0, color: '#ff9800', fontWeight: '500' }}>🔄 Research in progress...</p>
|
||||
<p style={{ margin: '8px 0 0 0', color: '#666', fontSize: '14px' }}>{currentMessage || 'Gathering research data...'}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} catch (renderError) {
|
||||
console.error('[ResearchAction] Error in render function:', renderError);
|
||||
// Return a safe fallback UI
|
||||
return (
|
||||
<div style={{ padding: '16px', backgroundColor: '#e8f5e8', borderRadius: '8px', border: '1px solid #4caf50', margin: '8px 0' }}>
|
||||
<p style={{ margin: 0, color: '#4caf50', fontWeight: '500' }}>✅ Research completed successfully!</p>
|
||||
<p style={{ margin: '8px 0 0 0', color: '#666', fontSize: '14px' }}>{latestMessage?.message || 'Research data is now available for your blog.'}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (polling.currentStatus === 'in_progress' || polling.currentStatus === 'running') {
|
||||
return (
|
||||
<div style={{ padding: '16px', backgroundColor: '#fff3e0', borderRadius: '8px', border: '1px solid #ff9800', margin: '8px 0' }}>
|
||||
<p style={{ margin: 0, color: '#ff9800', fontWeight: '500' }}>🔄 Research in progress...</p>
|
||||
<p style={{ margin: '8px 0 0 0', color: '#666', fontSize: '14px' }}>{currentMessage || 'Gathering research data...'}</p>
|
||||
<div style={{ padding: '16px', backgroundColor: '#f8f9fa', borderRadius: '8px', border: '1px solid #e0e0e0', margin: '8px 0' }}>
|
||||
<p style={{ margin: 0, color: '#666', fontSize: '14px' }}>🔍 Research form is loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useResearchPolling } from '../../hooks/usePolling';
|
||||
import { useBlogWriterResearchPolling } from '../../hooks/usePolling';
|
||||
import ResearchProgressModal from './ResearchProgressModal';
|
||||
import { BlogResearchResponse } from '../../services/blogWriterApi';
|
||||
import { researchCache } from '../../services/researchCache';
|
||||
@@ -18,7 +18,7 @@ export const ResearchPollingHandler: React.FC<ResearchPollingHandlerProps> = ({
|
||||
}) => {
|
||||
const [currentMessage, setCurrentMessage] = useState<string>('');
|
||||
|
||||
const polling = useResearchPolling({
|
||||
const polling = useBlogWriterResearchPolling({
|
||||
onProgress: (message) => {
|
||||
debug.log('[ResearchPollingHandler] progress', { message });
|
||||
setCurrentMessage(message);
|
||||
|
||||
Reference in New Issue
Block a user