merge: resolve conflicts favoring local for frontend packages
This commit is contained in:
@@ -39,6 +39,8 @@ import {
|
||||
} from '../../services/contentPlanningOrchestrator';
|
||||
import { StrategyCalendarProvider } from '../../contexts/StrategyCalendarContext';
|
||||
|
||||
// CopilotKit actions will be initialized in a separate component
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: number;
|
||||
@@ -99,6 +101,9 @@ const ContentPlanningDashboard: React.FC = () => {
|
||||
updateAIInsights
|
||||
} = useContentPlanningStore();
|
||||
|
||||
// CopilotKit actions will be initialized in a separate component
|
||||
// that's rendered inside the CopilotSidebar context
|
||||
|
||||
// Initialize orchestrator callbacks
|
||||
useEffect(() => {
|
||||
contentPlanningOrchestrator.setDataUpdateCallback((data) => {
|
||||
|
||||
@@ -73,6 +73,11 @@ import { useAIRefresh } from './ContentStrategyBuilder/hooks/useAIRefresh';
|
||||
import { useEventHandlers } from './ContentStrategyBuilder/hooks/useEventHandlers';
|
||||
import { useStrategyCreation } from './ContentStrategyBuilder/hooks/useStrategyCreation';
|
||||
|
||||
// CopilotKit actions are now initialized at the dashboard level
|
||||
|
||||
// Import CopilotKit hooks
|
||||
import { useCopilotReadable, useCopilotAdditionalInstructions } from "@copilotkit/react-core";
|
||||
|
||||
// Import extracted utilities
|
||||
import { getCategoryIcon, getCategoryColor, getCategoryName, getCategoryStatus } from './ContentStrategyBuilder/utils/categoryHelpers';
|
||||
import { getEducationalContent } from './ContentStrategyBuilder/utils/educationalContent';
|
||||
@@ -88,6 +93,8 @@ import StrategyDisplay from './ContentStrategyBuilder/components/StrategyDisplay
|
||||
import ErrorAlert from './ContentStrategyBuilder/components/ErrorAlert';
|
||||
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
||||
import CategoryDetailView from './ContentStrategyBuilder/components/CategoryDetailView';
|
||||
import { CopilotSidebar } from '@copilotkit/react-ui';
|
||||
import { useCopilotActions } from './ContentStrategyBuilder/CopilotActions';
|
||||
|
||||
const ContentStrategyBuilder: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -146,6 +153,24 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
setAIGenerating
|
||||
} = useEnhancedStrategyStore();
|
||||
|
||||
// Initialize Copilot actions (component is only rendered when Strategy Builder tab is active)
|
||||
useCopilotActions();
|
||||
|
||||
// Check if this component is currently visible (active tab)
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Use a small delay to ensure the component is actually rendered
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
setIsVisible(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [showAIRecommendations, setShowAIRecommendations] = useState(false);
|
||||
const [showDataSourceTransparency, setShowDataSourceTransparency] = useState(false);
|
||||
const [localEducationalContent, setLocalEducationalContent] = useState<any>(null);
|
||||
@@ -167,6 +192,111 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
handleShowEducationalInfo
|
||||
} = useEventHandlers();
|
||||
|
||||
// Provide context to CopilotKit for intelligent assistance
|
||||
console.log("🚀 Initializing CopilotKit context provision...");
|
||||
|
||||
// Provide form state context
|
||||
useCopilotReadable({
|
||||
description: "Current strategy form state and field data. This shows the current state of the 30+ strategy form fields.",
|
||||
value: {
|
||||
formData,
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
}),
|
||||
emptyFields: Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return !value || typeof value !== 'string' || value.trim() === '';
|
||||
}),
|
||||
categoryProgress: getCompletionStats().category_completion,
|
||||
activeCategory,
|
||||
formErrors,
|
||||
totalFields: 30,
|
||||
filledCount: Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
}).length
|
||||
}
|
||||
});
|
||||
|
||||
// Provide field definitions context
|
||||
useCopilotReadable({
|
||||
description: "Strategy field definitions and requirements. This contains all 30+ form fields with their descriptions, requirements, and categories.",
|
||||
value: STRATEGIC_INPUT_FIELDS.map(field => ({
|
||||
id: field.id,
|
||||
label: field.label,
|
||||
description: field.description,
|
||||
tooltip: field.tooltip,
|
||||
required: field.required,
|
||||
type: field.type,
|
||||
options: field.options,
|
||||
category: field.category,
|
||||
currentValue: formData[field.id] || null
|
||||
}))
|
||||
});
|
||||
|
||||
// Provide onboarding data context
|
||||
useCopilotReadable({
|
||||
description: "User onboarding data for personalization. This contains the user's website analysis, research preferences, and profile information.",
|
||||
value: {
|
||||
websiteAnalysis: personalizationData?.website_analysis,
|
||||
researchPreferences: personalizationData?.research_preferences,
|
||||
apiKeys: personalizationData?.api_keys,
|
||||
userProfile: personalizationData?.user_profile,
|
||||
hasOnboardingData: !!personalizationData
|
||||
}
|
||||
});
|
||||
|
||||
// Provide dynamic instructions
|
||||
useCopilotAdditionalInstructions({
|
||||
instructions: `
|
||||
You are ALwrity's Strategy Assistant, helping users create comprehensive content strategies.
|
||||
|
||||
IMPORTANT CONTEXT:
|
||||
- You are working with a form that has 30+ strategy fields
|
||||
- Current form completion: ${calculateCompletionPercentage()}%
|
||||
- Active category: ${activeCategory}
|
||||
- Filled fields: ${Object.keys(formData).filter(k => {
|
||||
const value = formData[k];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
}).length}/30
|
||||
- Empty fields: ${Object.keys(formData).filter(k => {
|
||||
const value = formData[k];
|
||||
return !value || typeof value !== 'string' || value.trim() === '';
|
||||
}).length}/30
|
||||
|
||||
AVAILABLE ACTIONS:
|
||||
- testAction: Test if actions are working
|
||||
- populateStrategyField: Fill a specific field
|
||||
- populateStrategyCategory: Fill multiple fields in a category
|
||||
- validateStrategyField: Check if a field is valid
|
||||
- reviewStrategy: Get overall strategy review
|
||||
- generateSuggestions: Get suggestions for a field
|
||||
- autoPopulateFromOnboarding: Auto-fill using onboarding data
|
||||
|
||||
SUGGESTIONS CONTEXT:
|
||||
- Users can click on suggestion buttons to quickly start common tasks
|
||||
- Suggestions are context-aware and change based on form completion
|
||||
- Always acknowledge when a user clicks a suggestion and explain what you'll do
|
||||
- Provide immediate value when suggestions are used
|
||||
|
||||
GUIDELINES:
|
||||
- When users ask about "fields", they mean the 30+ strategy form fields
|
||||
- Always reference real onboarding data when available
|
||||
- Provide specific, actionable suggestions
|
||||
- Explain the reasoning behind recommendations
|
||||
- Help users understand field relationships
|
||||
- Suggest next steps based on current progress
|
||||
- Use actual database data, never mock data
|
||||
- Be specific about which fields you're referring to
|
||||
- When users click suggestions, immediately execute the requested action
|
||||
- Provide clear feedback on what you're doing and why
|
||||
`
|
||||
});
|
||||
|
||||
console.log("✅ CopilotKit context provision initialized successfully");
|
||||
|
||||
// Create a state for educational modal that can be passed to both hooks
|
||||
const [showEducationalModal, setShowEducationalModal] = useState(false);
|
||||
const [showEnterpriseModal, setShowEnterpriseModal] = useState(false);
|
||||
@@ -405,8 +535,98 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
handleConfirmCategoryReview(activeCategory);
|
||||
};
|
||||
|
||||
// Generate comprehensive suggestions for all 7 CopilotKit actions
|
||||
const getSuggestions = () => {
|
||||
const filledFields = Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
}).length;
|
||||
const totalFields = Object.keys(STRATEGIC_INPUT_FIELDS).length;
|
||||
const emptyFields = totalFields - filledFields;
|
||||
const completionPercentage = calculateCompletionPercentage();
|
||||
|
||||
// All 7 CopilotKit actions as suggestions
|
||||
const allSuggestions = [
|
||||
{
|
||||
title: "🚀 Auto-populate from onboarding",
|
||||
message: "auto populate the strategy fields using my onboarding data with detailed progress tracking"
|
||||
},
|
||||
{
|
||||
title: "📊 Review my strategy",
|
||||
message: "review the overall strategy and identify gaps"
|
||||
},
|
||||
{
|
||||
title: "✅ Validate strategy quality",
|
||||
message: "validate my strategy fields and suggest improvements"
|
||||
},
|
||||
{
|
||||
title: "💡 Get field suggestions",
|
||||
message: "generate contextual suggestions for incomplete fields"
|
||||
},
|
||||
{
|
||||
title: "📝 Fill specific field",
|
||||
message: "help me populate a specific strategy field with intelligent data"
|
||||
},
|
||||
{
|
||||
title: "🎯 Populate category",
|
||||
message: "fill multiple fields in a specific category based on my description"
|
||||
},
|
||||
{
|
||||
title: "🧪 Test CopilotKit",
|
||||
message: "test if all CopilotKit actions are working properly"
|
||||
}
|
||||
];
|
||||
|
||||
// Add context-aware dynamic suggestions based on completion
|
||||
const dynamicSuggestions = [];
|
||||
|
||||
if (emptyFields > 0) {
|
||||
dynamicSuggestions.push({
|
||||
title: `🔧 Fill ${emptyFields} empty fields`,
|
||||
message: `help me populate the ${emptyFields} remaining empty fields in my strategy`
|
||||
});
|
||||
}
|
||||
|
||||
// Add category-specific suggestions
|
||||
if (activeCategory) {
|
||||
dynamicSuggestions.push({
|
||||
title: `🎯 Improve ${activeCategory}`,
|
||||
message: `generate suggestions for the ${activeCategory} category`
|
||||
});
|
||||
}
|
||||
|
||||
// Add next steps suggestion for high completion
|
||||
if (completionPercentage > 80) {
|
||||
dynamicSuggestions.push({
|
||||
title: "🚀 Next steps",
|
||||
message: "what are the next steps to complete my content strategy?"
|
||||
});
|
||||
}
|
||||
|
||||
// Combine all suggestions - prioritize dynamic ones first, then all actions
|
||||
const combinedSuggestions = [...dynamicSuggestions, ...allSuggestions];
|
||||
|
||||
// Return all suggestions (no limit) to show full CopilotKit capabilities
|
||||
return combinedSuggestions;
|
||||
};
|
||||
|
||||
// Memoize suggestions to prevent unnecessary re-renders
|
||||
const suggestions = useMemo(() => getSuggestions(), [formData, activeCategory, calculateCompletionPercentage]);
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<CopilotSidebar
|
||||
labels={{
|
||||
title: "ALwrity Strategy Assistant",
|
||||
initial: "Hi! I'm here to help you build your content strategy. I can auto-populate fields, provide guidance, and ensure your strategy is comprehensive. Check out the suggestions below to see all available actions, or just ask me anything!"
|
||||
}}
|
||||
suggestions={suggestions}
|
||||
observabilityHooks={{
|
||||
onChatExpanded: () => console.log("Strategy assistant opened"),
|
||||
onMessageSent: (message) => console.log("Strategy message sent", { message }),
|
||||
onFeedbackGiven: (messageId, type) => console.log("Strategy feedback", { messageId, type })
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 3 }}>
|
||||
{/* Header with Title (Region B) - Enhanced with Futuristic Styling */}
|
||||
<HeaderSection
|
||||
autoPopulatedFields={autoPopulatedFields}
|
||||
@@ -640,6 +860,7 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</CopilotSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCopilotAction } from "@copilotkit/react-core";
|
||||
import { contentPlanningApi } from '../../../../services/contentPlanningApi';
|
||||
import { useStrategyBuilderStore } from '../../../../stores/strategyBuilderStore';
|
||||
import { useEnhancedStrategyStore } from '../../../../stores/enhancedStrategyStore';
|
||||
|
||||
export const useCopilotActions = () => {
|
||||
console.log("CopilotActions hook initialized");
|
||||
|
||||
// Get store methods for updating form state
|
||||
const {
|
||||
formData,
|
||||
updateFormField,
|
||||
validateFormField,
|
||||
setError,
|
||||
autoPopulatedFields,
|
||||
dataSources,
|
||||
calculateCompletionPercentage,
|
||||
getCompletionStats
|
||||
} = useStrategyBuilderStore();
|
||||
|
||||
// Get enhanced strategy store methods for transparency modal
|
||||
const {
|
||||
setTransparencyModalOpen,
|
||||
setTransparencyGenerating,
|
||||
setTransparencyGenerationProgress,
|
||||
setCurrentPhase,
|
||||
clearTransparencyMessages,
|
||||
addTransparencyMessage,
|
||||
setAIGenerating
|
||||
} = useEnhancedStrategyStore();
|
||||
|
||||
// Helper function to trigger transparency modal flow (same as handleAIRefresh)
|
||||
const triggerTransparencyFlow = async (actionType: string, actionDescription: string) => {
|
||||
// Open transparency modal and initialize transparency state
|
||||
setTransparencyModalOpen(true);
|
||||
setTransparencyGenerating(true);
|
||||
setTransparencyGenerationProgress(0);
|
||||
setCurrentPhase(`${actionType}_initialization`);
|
||||
clearTransparencyMessages();
|
||||
addTransparencyMessage(`Starting ${actionDescription}...`);
|
||||
|
||||
setAIGenerating(true);
|
||||
|
||||
// Start transparency message polling for visual feedback
|
||||
const transparencyMessages = [
|
||||
{ type: `${actionType}_initialization`, message: `Starting ${actionDescription}...`, progress: 5 },
|
||||
{ type: `${actionType}_data_collection`, message: 'Collecting and analyzing data sources...', progress: 15 },
|
||||
{ type: `${actionType}_data_quality`, message: 'Assessing data quality and completeness...', progress: 25 },
|
||||
{ type: `${actionType}_context_analysis`, message: 'Analyzing business context and strategic framework...', progress: 35 },
|
||||
{ type: `${actionType}_strategy_generation`, message: 'Generating strategic insights and recommendations...', progress: 45 },
|
||||
{ type: `${actionType}_field_generation`, message: 'Generating individual strategy input fields...', progress: 55 },
|
||||
{ type: `${actionType}_quality_validation`, message: 'Validating generated strategy inputs...', progress: 65 },
|
||||
{ type: `${actionType}_alignment_check`, message: 'Checking strategy alignment and consistency...', progress: 75 },
|
||||
{ type: `${actionType}_final_review`, message: 'Performing final review and optimization...', progress: 85 },
|
||||
{ type: `${actionType}_complete`, message: `${actionDescription} completed successfully...`, progress: 95 }
|
||||
];
|
||||
|
||||
let messageIndex = 0;
|
||||
const transparencyInterval = setInterval(() => {
|
||||
if (messageIndex < transparencyMessages.length) {
|
||||
const message = transparencyMessages[messageIndex];
|
||||
setCurrentPhase(message.type);
|
||||
addTransparencyMessage(message.message);
|
||||
setTransparencyGenerationProgress(message.progress);
|
||||
messageIndex++;
|
||||
} else {
|
||||
clearInterval(transparencyInterval);
|
||||
}
|
||||
}, 2000); // Send a message every 2 seconds for better UX
|
||||
|
||||
return { transparencyInterval };
|
||||
};
|
||||
|
||||
// Action 1: Test action (no parameters)
|
||||
const testAction = useCallback(async () => {
|
||||
console.log("🎉 Test action executed successfully!");
|
||||
return {
|
||||
success: true,
|
||||
message: "Test action worked! You can now use CopilotKit actions.",
|
||||
timestamp: new Date().toISOString(),
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => formData[key]),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
}, [formData, calculateCompletionPercentage]);
|
||||
|
||||
// Action 2: Populate individual field
|
||||
const populateStrategyField = useCallback(async ({ fieldId, value, reasoning }: any) => {
|
||||
try {
|
||||
console.log(`📝 Populating field ${fieldId} with value: ${value}`);
|
||||
|
||||
// Call backend API for intelligent field population
|
||||
const response = await contentPlanningApi.generateCategoryData(
|
||||
'individual_field',
|
||||
`Populate ${fieldId} with: ${value}. Reasoning: ${reasoning || 'User request'}`,
|
||||
formData
|
||||
);
|
||||
|
||||
// Update form state with the new value
|
||||
updateFormField(fieldId, value);
|
||||
|
||||
// Validate the field after population
|
||||
const validation = validateFormField(fieldId);
|
||||
|
||||
if (reasoning) {
|
||||
console.log(`💭 Reasoning: ${reasoning}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Field ${fieldId} populated successfully with: ${value}`,
|
||||
fieldId,
|
||||
value,
|
||||
reasoning,
|
||||
validation,
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => formData[key]),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Failed to populate field ${fieldId}:`, error);
|
||||
setError(`Failed to populate field ${fieldId}: ${error.message}`);
|
||||
return { success: false, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}, [formData, updateFormField, validateFormField, setError, calculateCompletionPercentage]);
|
||||
|
||||
// Action 3: Bulk populate category
|
||||
const populateStrategyCategory = useCallback(async ({ category, userDescription }: any) => {
|
||||
try {
|
||||
console.log(`📊 Populating category ${category} with description: ${userDescription}`);
|
||||
|
||||
// Start transparency flow for category population
|
||||
const { transparencyInterval } = await triggerTransparencyFlow('category_population', `Category population for ${category}`);
|
||||
|
||||
// Call backend API to generate category data
|
||||
const response = await contentPlanningApi.generateCategoryData(
|
||||
category,
|
||||
userDescription,
|
||||
formData
|
||||
);
|
||||
|
||||
// Clear the transparency interval since we got the response
|
||||
clearInterval(transparencyInterval);
|
||||
|
||||
// Update all fields in the category
|
||||
const populatedFields: string[] = [];
|
||||
if (response.data && response.data.data) {
|
||||
Object.entries(response.data.data).forEach(([fieldId, value]) => {
|
||||
updateFormField(fieldId, value as string);
|
||||
populatedFields.push(fieldId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add final completion message
|
||||
addTransparencyMessage(`✅ Category ${category} populated successfully! Generated ${populatedFields.length} fields.`);
|
||||
setTransparencyGenerationProgress(100);
|
||||
setCurrentPhase('Complete');
|
||||
|
||||
// Reset generation state
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Category ${category} populated successfully based on: ${userDescription}`,
|
||||
category,
|
||||
userDescription,
|
||||
populatedFields,
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
}),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Failed to populate category ${category}:`, error);
|
||||
setError(`Failed to populate category ${category}: ${error.message}`);
|
||||
setTransparencyModalOpen(false);
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
return { success: false, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}, [formData, updateFormField, setError, calculateCompletionPercentage, setTransparencyModalOpen, setTransparencyGenerating, setTransparencyGenerationProgress, setCurrentPhase, clearTransparencyMessages, addTransparencyMessage, setAIGenerating]);
|
||||
|
||||
// Action 4: Validate field
|
||||
const validateStrategyField = useCallback(async ({ fieldId }: any) => {
|
||||
try {
|
||||
console.log(`✅ Validating field ${fieldId}`);
|
||||
|
||||
const currentValue = formData[fieldId];
|
||||
|
||||
// Call backend API for field validation
|
||||
const response = await contentPlanningApi.validateField(fieldId, currentValue);
|
||||
|
||||
// Also validate locally
|
||||
const localValidation = validateFormField(fieldId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
validation: {
|
||||
isValid: localValidation,
|
||||
suggestion: response.data?.suggestion || `Field ${fieldId} looks good! Consider adding more specific details if needed.`,
|
||||
confidence: response.data?.confidence || 0.8
|
||||
},
|
||||
fieldId,
|
||||
currentValue,
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => formData[key]),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Failed to validate field ${fieldId}:`, error);
|
||||
setError(`Failed to validate field ${fieldId}: ${error.message}`);
|
||||
return { success: false, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}, [formData, validateFormField, setError, calculateCompletionPercentage]);
|
||||
|
||||
// Action 5: Review strategy
|
||||
const reviewStrategy = useCallback(async () => {
|
||||
try {
|
||||
console.log("🔍 Reviewing strategy");
|
||||
|
||||
// Call backend API for strategy analysis
|
||||
const response = await contentPlanningApi.analyzeStrategy(formData);
|
||||
|
||||
const completionStats = getCompletionStats();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
review: {
|
||||
completeness: calculateCompletionPercentage(),
|
||||
suggestions: response.data?.suggestions || [
|
||||
"Your strategy looks good overall!",
|
||||
"Consider adding more specific target audience details",
|
||||
"Include measurable goals and KPIs"
|
||||
],
|
||||
missingFields: response.data?.missingFields || [],
|
||||
improvements: response.data?.improvements || [],
|
||||
categoryProgress: completionStats.category_completion,
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => formData[key]),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("❌ Failed to review strategy:", error);
|
||||
setError(`Failed to review strategy: ${error.message}`);
|
||||
return { success: false, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}, [formData, calculateCompletionPercentage, getCompletionStats, setError]);
|
||||
|
||||
// Action 6: Generate suggestions
|
||||
const generateSuggestions = useCallback(async ({ fieldId }: any) => {
|
||||
try {
|
||||
console.log(`💡 Generating suggestions for field ${fieldId}`);
|
||||
|
||||
// Call backend API for field suggestions
|
||||
const response = await contentPlanningApi.generateFieldSuggestions(fieldId, formData);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
suggestions: response.data?.suggestions || [
|
||||
`Suggestion 1 for ${fieldId}: Focus on specific, measurable outcomes`,
|
||||
`Suggestion 2 for ${fieldId}: Consider your target audience's pain points`,
|
||||
`Suggestion 3 for ${fieldId}: Align with your overall business objectives`
|
||||
],
|
||||
reasoning: response.data?.reasoning || `Based on your current strategy context, here are some suggestions for ${fieldId}`,
|
||||
confidence: response.data?.confidence || 0.8,
|
||||
fieldId,
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => formData[key]),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Failed to generate suggestions for ${fieldId}:`, error);
|
||||
setError(`Failed to generate suggestions for ${fieldId}: ${error.message}`);
|
||||
return { success: false, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}, [formData, calculateCompletionPercentage, setError]);
|
||||
|
||||
// Action 7: Auto-populate from onboarding
|
||||
const autoPopulateFromOnboarding = useCallback(async () => {
|
||||
try {
|
||||
console.log("🔄 Auto-populating from onboarding data");
|
||||
|
||||
// Start transparency flow (same as Refresh & Autofill button)
|
||||
const { transparencyInterval } = await triggerTransparencyFlow('autofill', 'Auto-population from onboarding data');
|
||||
|
||||
// Get current form data to see what's already filled
|
||||
const currentFilledFields = Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
});
|
||||
const emptyFields = Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return !value || typeof value !== 'string' || value.trim() === '';
|
||||
});
|
||||
|
||||
// Call the same backend API as the Refresh & Autofill button
|
||||
const response = await contentPlanningApi.refreshAutofill(1, true, true);
|
||||
|
||||
// Clear the transparency interval since we got the response
|
||||
clearInterval(transparencyInterval);
|
||||
|
||||
// Process the response (same logic as handleAIRefresh)
|
||||
if (response) {
|
||||
const payload = response;
|
||||
const fields = payload.fields || {};
|
||||
const sources = payload.sources || {};
|
||||
const inputDataPoints = payload.input_data_points || {};
|
||||
const meta = payload.meta || {};
|
||||
|
||||
console.log('🎯 CopilotKit Auto-population - Generated fields:', Object.keys(fields).length);
|
||||
|
||||
// Check if AI generation failed
|
||||
if (meta.error || !meta.ai_used) {
|
||||
console.error('❌ AI generation failed:', meta.error || 'AI not used');
|
||||
setError(`AI generation failed: ${meta.error || 'AI was not used for generation. Please try again.'}`);
|
||||
setTransparencyModalOpen(false);
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
return { success: false, message: 'AI generation failed. Please try again.' };
|
||||
}
|
||||
|
||||
// Check if we have any fields generated
|
||||
const fieldsCount = Object.keys(fields).length;
|
||||
if (fieldsCount === 0) {
|
||||
console.error('❌ No fields generated');
|
||||
setError('No fields were generated. Please try again.');
|
||||
setTransparencyModalOpen(false);
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
return { success: false, message: 'No fields generated. Please try again.' };
|
||||
}
|
||||
|
||||
console.log(`✅ AI generation successful - ${fieldsCount} fields generated`);
|
||||
|
||||
// Validate data source
|
||||
if (meta.data_source === 'ai_generation_failed' || meta.data_source === 'ai_generation_error') {
|
||||
console.error('❌ AI generation failed:', meta.data_source);
|
||||
setError(`AI generation failed: ${meta.error || 'Invalid data source. Please try again.'}`);
|
||||
setTransparencyModalOpen(false);
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
return { success: false, message: 'AI generation failed. Please try again.' };
|
||||
}
|
||||
|
||||
const fieldValues: Record<string, any> = {};
|
||||
const confidenceScores: Record<string, number> = {};
|
||||
|
||||
Object.keys(fields).forEach((fieldId) => {
|
||||
const fieldData = fields[fieldId];
|
||||
|
||||
if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) {
|
||||
fieldValues[fieldId] = fieldData.value;
|
||||
|
||||
// Extract confidence score if available
|
||||
if (fieldData.confidence) {
|
||||
confidenceScores[fieldId] = fieldData.confidence;
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ Field ${fieldId} has invalid structure`);
|
||||
}
|
||||
});
|
||||
|
||||
// Update the store with the new data - COMPLETELY REPLACE old data
|
||||
useStrategyBuilderStore.setState((state) => {
|
||||
const newState = {
|
||||
autoPopulatedFields: fieldValues,
|
||||
dataSources: sources,
|
||||
inputDataPoints: inputDataPoints,
|
||||
confidenceScores: confidenceScores,
|
||||
formData: { ...state.formData, ...fieldValues } // Keep existing manual edits
|
||||
};
|
||||
console.log('✅ Store updated with fresh AI data:', Object.keys(fieldValues).length, 'fields');
|
||||
return newState;
|
||||
});
|
||||
|
||||
// Add final completion message
|
||||
addTransparencyMessage(`✅ AI generation completed successfully! Generated ${Object.keys(fieldValues).length} real AI values.`);
|
||||
setTransparencyGenerationProgress(100);
|
||||
setCurrentPhase('Complete');
|
||||
|
||||
// Update session storage with fresh autofill timestamp
|
||||
sessionStorage.setItem('lastAutofillTime', new Date().toISOString());
|
||||
|
||||
// Reset generation state
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Auto-population completed successfully! Generated ${Object.keys(fieldValues).length} fields using your onboarding data.`,
|
||||
populatedFields: Object.keys(fieldValues),
|
||||
emptyFieldsRemaining: emptyFields.filter(field => !Object.keys(fieldValues).includes(field)),
|
||||
timestamp: new Date().toISOString(),
|
||||
formStatus: {
|
||||
completionPercentage: calculateCompletionPercentage(),
|
||||
filledFields: Object.keys(formData).filter(key => {
|
||||
const value = formData[key];
|
||||
return value && typeof value === 'string' && value.trim() !== '';
|
||||
}),
|
||||
totalFields: 30
|
||||
}
|
||||
};
|
||||
} else {
|
||||
throw new Error('Invalid response from AI refresh endpoint');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ Failed to auto-populate:", error);
|
||||
setError(`Failed to auto-populate: ${error.message}`);
|
||||
setTransparencyModalOpen(false);
|
||||
setAIGenerating(false);
|
||||
setTransparencyGenerating(false);
|
||||
return { success: false, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}, [formData, updateFormField, calculateCompletionPercentage, setError, setTransparencyModalOpen, setTransparencyGenerating, setTransparencyGenerationProgress, setCurrentPhase, clearTransparencyMessages, addTransparencyMessage, setAIGenerating]);
|
||||
|
||||
// Call useCopilotAction hooks unconditionally - they will handle context availability internally
|
||||
// This is the only way to comply with React hooks rules
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "testAction",
|
||||
description: "A simple test action to verify CopilotKit functionality. Use this to test if the assistant can execute actions and understand the current form state.",
|
||||
handler: testAction
|
||||
});
|
||||
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "populateStrategyField",
|
||||
description: "Intelligently populate a strategy field with contextual data. Use this to fill in specific form fields. The assistant will understand the current form state and provide appropriate values.",
|
||||
parameters: [
|
||||
{ name: "fieldId", type: "string", required: true, description: "The ID of the field to populate (e.g., 'business_objectives', 'target_audience', 'content_goals')" },
|
||||
{ name: "value", type: "string", required: true, description: "The value to populate the field with" },
|
||||
{ name: "reasoning", type: "string", required: false, description: "Explanation for why this value was chosen" }
|
||||
],
|
||||
handler: populateStrategyField
|
||||
});
|
||||
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "populateStrategyCategory",
|
||||
description: "Populate all fields in a specific category based on user description. Use this to fill multiple related fields at once. Categories include: 'business_context', 'audience_intelligence', 'competitive_intelligence', 'content_strategy', 'performance_analytics'.",
|
||||
parameters: [
|
||||
{ name: "category", type: "string", required: true, description: "The category of fields to populate (e.g., 'business_context', 'audience_intelligence', 'content_strategy')" },
|
||||
{ name: "userDescription", type: "string", required: true, description: "User's description of what they want to achieve with this category" }
|
||||
],
|
||||
handler: populateStrategyCategory
|
||||
});
|
||||
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "validateStrategyField",
|
||||
description: "Validate a strategy field and provide improvement suggestions. Use this to check if a field value is appropriate and get suggestions for improvement.",
|
||||
parameters: [
|
||||
{ name: "fieldId", type: "string", required: true, description: "The ID of the field to validate" }
|
||||
],
|
||||
handler: validateStrategyField
|
||||
});
|
||||
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "reviewStrategy",
|
||||
description: "Comprehensive strategy review with AI analysis. Use this to get a complete overview of your strategy's completeness, coherence, and quality. The assistant will analyze all 30 fields and provide detailed feedback.",
|
||||
handler: reviewStrategy
|
||||
});
|
||||
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "generateSuggestions",
|
||||
description: "Generate contextual suggestions for incomplete fields. Use this to get ideas for specific fields based on your current strategy context and onboarding data.",
|
||||
parameters: [
|
||||
{ name: "fieldId", type: "string", required: true, description: "The ID of the field to generate suggestions for" }
|
||||
],
|
||||
handler: generateSuggestions
|
||||
});
|
||||
|
||||
(useCopilotAction as unknown as (config: any) => void)({
|
||||
name: "autoPopulateFromOnboarding",
|
||||
description: "Auto-populate strategy fields using onboarding data. Use this to automatically fill fields based on your onboarding information, website analysis, and research preferences.",
|
||||
handler: autoPopulateFromOnboarding
|
||||
});
|
||||
|
||||
// Return action handlers for direct use if needed
|
||||
return {
|
||||
testAction,
|
||||
populateStrategyField,
|
||||
populateStrategyCategory,
|
||||
validateStrategyField,
|
||||
reviewStrategy,
|
||||
generateSuggestions,
|
||||
autoPopulateFromOnboarding
|
||||
};
|
||||
};
|
||||
@@ -74,6 +74,63 @@ const DataTransparencyPanel: React.FC<DataTransparencyPanelProps> = ({
|
||||
};
|
||||
}, [refreshInterval]);
|
||||
|
||||
const convertMonitoringTasksToTransparencyData = (monitoringTasks: any[]) => {
|
||||
try {
|
||||
// Group tasks by component
|
||||
const tasksByComponent = monitoringTasks.reduce((acc, task) => {
|
||||
const component = task.component || 'General';
|
||||
if (!acc[component]) {
|
||||
acc[component] = [];
|
||||
}
|
||||
acc[component].push(task);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Convert to transparency data format
|
||||
return Object.entries(tasksByComponent).map(([component, tasks]: [string, any]) => ({
|
||||
metricName: component,
|
||||
currentValue: tasks.length,
|
||||
unit: "tasks",
|
||||
dataFreshness: {
|
||||
lastUpdated: new Date().toISOString(),
|
||||
updateFrequency: "Real-time",
|
||||
dataSource: "Monitoring Tasks"
|
||||
},
|
||||
measurementMethod: "AI-powered monitoring",
|
||||
successCriteria: `${tasks.length} active monitoring tasks`,
|
||||
monitoringTasks: tasks.map((task: any) => ({
|
||||
title: task.title,
|
||||
description: task.description,
|
||||
assignee: task.assignee,
|
||||
frequency: task.frequency,
|
||||
metric: task.metric,
|
||||
measurementMethod: task.measurementMethod,
|
||||
successCriteria: task.successCriteria,
|
||||
alertThreshold: task.alertThreshold,
|
||||
actionableInsights: task.actionableInsights,
|
||||
status: task.status || 'active',
|
||||
lastExecuted: task.last_executed,
|
||||
nextExecution: task.next_execution
|
||||
})),
|
||||
insights: [
|
||||
`Active monitoring for ${component} with ${tasks.length} tasks`,
|
||||
"AI-powered performance tracking enabled",
|
||||
"Real-time alerts and notifications configured",
|
||||
`Monitoring frequency: ${tasks[0]?.frequency || 'Monthly'}`
|
||||
],
|
||||
recommendations: [
|
||||
"Monitor task execution status regularly",
|
||||
"Review performance metrics weekly",
|
||||
"Adjust thresholds based on performance trends",
|
||||
`Focus on ${tasks.filter((t: any) => t.assignee === 'ALwrity').length} AI-managed tasks`
|
||||
]
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error converting monitoring tasks to transparency data:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const loadTransparencyData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -87,7 +144,30 @@ const DataTransparencyPanel: React.FC<DataTransparencyPanelProps> = ({
|
||||
return;
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.warn('API call failed, falling back to mock data:', apiError);
|
||||
console.warn('API call failed, trying localStorage:', apiError);
|
||||
// Try to load from localStorage
|
||||
const analyticsData = localStorage.getItem('strategy_analytics_data');
|
||||
if (analyticsData) {
|
||||
try {
|
||||
const data = JSON.parse(analyticsData);
|
||||
console.log('Loaded analytics data from localStorage:', data);
|
||||
|
||||
// Extract monitoring tasks from analytics data
|
||||
const monitoringTasks = data.monitoring_tasks || [];
|
||||
console.log('Extracted monitoring tasks:', monitoringTasks);
|
||||
|
||||
if (monitoringTasks.length > 0) {
|
||||
// Convert monitoring tasks to transparency data format
|
||||
const transparencyDataFromTasks = convertMonitoringTasksToTransparencyData(monitoringTasks);
|
||||
setTransparencyData(transparencyDataFromTasks);
|
||||
return;
|
||||
} else {
|
||||
console.warn('No monitoring tasks found in analytics data');
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.warn('Failed to parse analytics data from localStorage:', parseError);
|
||||
}
|
||||
}
|
||||
// Continue to mock data as fallback
|
||||
}
|
||||
|
||||
|
||||
@@ -99,12 +99,55 @@ const EnhancedPerformanceVisualization: React.FC<EnhancedPerformanceVisualizatio
|
||||
setLoadingQuality(true);
|
||||
setError(null);
|
||||
|
||||
// Call the quality analysis API
|
||||
const response = await strategyMonitoringApi.getQualityAnalysis(strategyId);
|
||||
setQualityAnalysis(response.data);
|
||||
// Try to get quality analysis from monitoring data first
|
||||
const monitoringData = localStorage.getItem('strategy_analytics_data');
|
||||
|
||||
if (monitoringData) {
|
||||
const data = JSON.parse(monitoringData);
|
||||
const monitoringPlan = data.monitoring_plan;
|
||||
|
||||
// Extract quality metrics from monitoring plan
|
||||
const qualityData: QualityAnalysisData = {
|
||||
overall_score: data.performance_metrics?.confidence_score || 75,
|
||||
overall_status: data.performance_metrics?.confidence_score >= 80 ? 'excellent' :
|
||||
data.performance_metrics?.confidence_score >= 60 ? 'good' : 'needs_attention',
|
||||
metrics: [
|
||||
{
|
||||
name: 'Strategic Completeness',
|
||||
score: 85,
|
||||
status: 'excellent',
|
||||
description: 'Strategy covers all key components',
|
||||
recommendations: monitoringPlan?.recommendations || []
|
||||
},
|
||||
{
|
||||
name: 'Content Quality',
|
||||
score: data.performance_metrics?.content_quality_score || 75,
|
||||
status: data.performance_metrics?.content_quality_score >= 80 ? 'excellent' : 'good',
|
||||
description: 'Content meets quality standards',
|
||||
recommendations: ['Continue monitoring content performance']
|
||||
},
|
||||
{
|
||||
name: 'Engagement Metrics',
|
||||
score: data.performance_metrics?.engagement_rate || 70,
|
||||
status: data.performance_metrics?.engagement_rate >= 75 ? 'good' : 'needs_attention',
|
||||
description: 'Audience engagement levels',
|
||||
recommendations: ['Focus on improving engagement rates']
|
||||
}
|
||||
],
|
||||
recommendations: monitoringPlan?.recommendations || [],
|
||||
confidence_score: data.performance_metrics?.confidence_score || 75
|
||||
};
|
||||
|
||||
setQualityAnalysis(qualityData);
|
||||
console.log('✅ Quality analysis loaded from monitoring data');
|
||||
} else {
|
||||
// Fallback to API call if no monitoring data
|
||||
console.log('⚠️ No monitoring data found, skipping quality analysis');
|
||||
setQualityAnalysis(null);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to load quality analysis');
|
||||
console.error('Error loading quality analysis:', err);
|
||||
console.warn('⚠️ Error loading quality analysis from monitoring data:', err);
|
||||
setQualityAnalysis(null);
|
||||
} finally {
|
||||
setLoadingQuality(false);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { motion, AnimatePresence, easeOut } from 'framer-motion';
|
||||
import StrategyActivationModal from '../../StrategyActivationModal';
|
||||
import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
|
||||
|
||||
|
||||
interface EnhancedStrategyActivationButtonProps {
|
||||
strategyData: any;
|
||||
strategyConfirmed: boolean;
|
||||
@@ -45,16 +46,7 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
|
||||
console.log('🎯 EnhancedStrategyActivationButton: handleActivation called');
|
||||
if (isLoading || disabled) return;
|
||||
|
||||
// For now, directly call the activation function instead of opening the modal
|
||||
console.log('🎯 EnhancedStrategyActivationButton: Directly calling onConfirmStrategy');
|
||||
try {
|
||||
await onConfirmStrategy();
|
||||
console.log('🎯 EnhancedStrategyActivationButton: onConfirmStrategy completed successfully');
|
||||
} catch (error) {
|
||||
console.error('🎯 EnhancedStrategyActivationButton: onConfirmStrategy failed:', error);
|
||||
}
|
||||
|
||||
// Open the activation modal instead of calling onConfirmStrategy directly
|
||||
// Open the activation modal to show monitoring setup
|
||||
console.log('🎯 EnhancedStrategyActivationButton: Opening activation modal');
|
||||
setShowActivationModal(true);
|
||||
};
|
||||
@@ -70,21 +62,59 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
|
||||
const handleSetupMonitoring = async (monitoringPlan: any) => {
|
||||
try {
|
||||
console.log('🎯 EnhancedStrategyActivationButton: handleSetupMonitoring called');
|
||||
// Call the actual activation function
|
||||
|
||||
// Get strategy ID
|
||||
const strategyId = strategyData?.id || 1;
|
||||
|
||||
// Step 1: Generate monitoring plan if not provided
|
||||
let finalMonitoringPlan = monitoringPlan;
|
||||
if (!finalMonitoringPlan) {
|
||||
console.log('🎯 Generating monitoring plan...');
|
||||
try {
|
||||
const response = await fetch(`/api/content-planning/strategy/${strategyId}/generate-monitoring-plan`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
const planResponse = await response.json();
|
||||
finalMonitoringPlan = planResponse.data;
|
||||
console.log('🎯 Monitoring plan generated:', finalMonitoringPlan);
|
||||
} catch (error) {
|
||||
console.warn('Could not generate monitoring plan:', error);
|
||||
// Continue without monitoring plan
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Activate strategy with monitoring plan
|
||||
console.log('🎯 Activating strategy with monitoring...');
|
||||
try {
|
||||
const response = await fetch(`/api/content-planning/strategy/${strategyId}/activate-with-monitoring`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(finalMonitoringPlan)
|
||||
});
|
||||
const activationResponse = await response.json();
|
||||
console.log('🎯 Strategy activated with monitoring:', activationResponse);
|
||||
} catch (error) {
|
||||
console.warn('Could not activate strategy with monitoring:', error);
|
||||
// Continue with local activation only
|
||||
}
|
||||
|
||||
// Step 3: Call the local confirmation function
|
||||
console.log('🎯 EnhancedStrategyActivationButton: Calling onConfirmStrategy()');
|
||||
await onConfirmStrategy();
|
||||
console.log('🎯 EnhancedStrategyActivationButton: onConfirmStrategy() completed');
|
||||
|
||||
// Update strategy state to confirmed/active
|
||||
// This will trigger UI updates in parent components
|
||||
// Step 4: Update analytics and monitoring data
|
||||
console.log('🎯 Setting up analytics and monitoring...');
|
||||
await setupAnalyticsAndMonitoring(strategyId, finalMonitoringPlan);
|
||||
|
||||
// Show success state
|
||||
setIsSuccess(true);
|
||||
setShowSuccessMessage(true);
|
||||
|
||||
// Use navigation orchestrator to handle successful activation
|
||||
const strategyId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1';
|
||||
navigationOrchestrator.handleStrategyActivationSuccess(strategyId, strategyData);
|
||||
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1';
|
||||
navigationOrchestrator.handleStrategyActivationSuccess(userId, strategyData);
|
||||
|
||||
// Reset after success animation
|
||||
setTimeout(() => {
|
||||
@@ -98,6 +128,34 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
|
||||
}
|
||||
};
|
||||
|
||||
const setupAnalyticsAndMonitoring = async (strategyId: number, monitoringPlan: any) => {
|
||||
try {
|
||||
console.log('🎯 Setting up analytics and monitoring for strategy:', strategyId);
|
||||
|
||||
// Update analytics page with monitoring data
|
||||
// This will populate the analytics dashboard with the new monitoring tasks
|
||||
const analyticsData = {
|
||||
strategy_id: strategyId,
|
||||
monitoring_plan: monitoringPlan,
|
||||
activation_date: new Date().toISOString(),
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
// Store analytics data in localStorage for the analytics page to access
|
||||
localStorage.setItem('strategy_analytics_data', JSON.stringify(analyticsData));
|
||||
|
||||
// Also store monitoring tasks for the data transparency panel
|
||||
const monitoringTasks = monitoringPlan?.monitoringTasks || [];
|
||||
localStorage.setItem('strategy_monitoring_tasks', JSON.stringify(monitoringTasks));
|
||||
|
||||
console.log('🎯 Analytics and monitoring setup completed');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error setting up analytics and monitoring:', error);
|
||||
// Don't fail the activation if analytics setup fails
|
||||
}
|
||||
};
|
||||
|
||||
// Success animation variants
|
||||
const successVariants = {
|
||||
initial: { scale: 0, opacity: 0 },
|
||||
|
||||
@@ -293,6 +293,33 @@ const MetricTransparencyCard: React.FC<MetricTransparencyCardProps> = ({
|
||||
{task.description}
|
||||
</Typography>
|
||||
|
||||
<Box display="flex" gap={2} mb={1}>
|
||||
{task.frequency && (
|
||||
<Chip
|
||||
label={`${task.frequency}`}
|
||||
size="small"
|
||||
icon={<ScheduleIcon />}
|
||||
sx={{
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
color: 'white',
|
||||
fontSize: '0.6rem'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{task.metric && (
|
||||
<Chip
|
||||
label={`${task.metric}`}
|
||||
size="small"
|
||||
icon={<AssessmentIcon />}
|
||||
sx={{
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
color: 'white',
|
||||
fontSize: '0.6rem'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2} sx={{ width: '100%' }}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: 'rgba(255,255,255,0.7)' }}>
|
||||
@@ -310,6 +337,26 @@ const MetricTransparencyCard: React.FC<MetricTransparencyCardProps> = ({
|
||||
{task.successCriteria}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{task.alertThreshold && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: 'rgba(255,255,255,0.7)' }}>
|
||||
Alert Threshold
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontSize: '0.75rem', opacity: 0.8 }}>
|
||||
{task.alertThreshold}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
{task.actionableInsights && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: 'rgba(255,255,255,0.7)' }}>
|
||||
Actionable Insights
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontSize: '0.75rem', opacity: 0.8 }}>
|
||||
{task.actionableInsights}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</ListItem>
|
||||
))}
|
||||
|
||||
@@ -57,11 +57,51 @@ const TrendAnalysis: React.FC<TrendAnalysisProps> = ({
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Call the API to get trend data
|
||||
const response = await strategyMonitoringApi.getTrendData(strategyId, timeRange);
|
||||
setTrendData(response.data);
|
||||
// Try to get trend data from monitoring data first
|
||||
const monitoringData = localStorage.getItem('strategy_analytics_data');
|
||||
|
||||
if (monitoringData) {
|
||||
const data = JSON.parse(monitoringData);
|
||||
const performanceMetrics = data.performance_metrics;
|
||||
|
||||
// Generate trend data from monitoring metrics
|
||||
const trendData: TrendData[] = [
|
||||
{
|
||||
date: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 30 days ago
|
||||
traffic_growth: (performanceMetrics?.traffic_growth || 0) - 5,
|
||||
engagement_rate: (performanceMetrics?.engagement_rate || 0) - 3,
|
||||
conversion_rate: (performanceMetrics?.conversion_rate || 0) - 2,
|
||||
content_quality_score: (performanceMetrics?.content_quality_score || 0) - 5,
|
||||
strategy_adoption_rate: 75
|
||||
},
|
||||
{
|
||||
date: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 15 days ago
|
||||
traffic_growth: (performanceMetrics?.traffic_growth || 0) - 2,
|
||||
engagement_rate: (performanceMetrics?.engagement_rate || 0) - 1,
|
||||
conversion_rate: (performanceMetrics?.conversion_rate || 0) - 1,
|
||||
content_quality_score: (performanceMetrics?.content_quality_score || 0) - 2,
|
||||
strategy_adoption_rate: 80
|
||||
},
|
||||
{
|
||||
date: new Date().toISOString().split('T')[0], // Today
|
||||
traffic_growth: performanceMetrics?.traffic_growth || 0,
|
||||
engagement_rate: performanceMetrics?.engagement_rate || 0,
|
||||
conversion_rate: performanceMetrics?.conversion_rate || 0,
|
||||
content_quality_score: performanceMetrics?.content_quality_score || 0,
|
||||
strategy_adoption_rate: 85
|
||||
}
|
||||
];
|
||||
|
||||
setTrendData(trendData);
|
||||
console.log('✅ Trend data loaded from monitoring data');
|
||||
} else {
|
||||
// Fallback to empty data if no monitoring data
|
||||
console.log('⚠️ No monitoring data found, using empty trend data');
|
||||
setTrendData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading trend data:', error);
|
||||
console.warn('⚠️ Error loading trend data from monitoring data:', error);
|
||||
setTrendData([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
CircularProgress,
|
||||
LinearProgress,
|
||||
Tabs,
|
||||
Tab
|
||||
Tab,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import {
|
||||
TrendingUp as TrendingUpIcon,
|
||||
@@ -21,7 +22,8 @@ import {
|
||||
Assessment as AssessmentIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
Timeline as TimelineIcon,
|
||||
AutoAwesome as AutoAwesomeIcon
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
Refresh as RefreshIcon
|
||||
} from '@mui/icons-material';
|
||||
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
|
||||
import { contentPlanningApi } from '../../../services/contentPlanningApi';
|
||||
@@ -53,18 +55,18 @@ function TabPanel(props: TabPanelProps) {
|
||||
|
||||
const AnalyticsTab: React.FC = () => {
|
||||
const {
|
||||
performanceMetrics,
|
||||
aiInsights,
|
||||
currentStrategy,
|
||||
loading,
|
||||
error,
|
||||
loadAIInsights,
|
||||
loadAIRecommendations
|
||||
error: storeError
|
||||
} = useContentPlanningStore();
|
||||
|
||||
const [analyticsData, setAnalyticsData] = useState<any>(null);
|
||||
const [dataLoading, setDataLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
// Cache for analytics data to prevent redundant calls
|
||||
const [lastLoadTime, setLastLoadTime] = useState<number>(0);
|
||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
useEffect(() => {
|
||||
loadAnalyticsData();
|
||||
@@ -72,35 +74,89 @@ const AnalyticsTab: React.FC = () => {
|
||||
|
||||
const loadAnalyticsData = async () => {
|
||||
try {
|
||||
// Check if we have recent cached data to avoid redundant calls
|
||||
const now = Date.now();
|
||||
if (analyticsData && (now - lastLoadTime) < CACHE_DURATION) {
|
||||
console.log('🎯 Using cached analytics data (cache valid for', Math.round((CACHE_DURATION - (now - lastLoadTime)) / 1000), 'seconds)');
|
||||
return;
|
||||
}
|
||||
|
||||
setDataLoading(true);
|
||||
setError(null);
|
||||
|
||||
console.log('Loading analytics data...');
|
||||
console.log('🎯 Loading analytics data...');
|
||||
|
||||
// Load AI insights and recommendations
|
||||
await Promise.all([
|
||||
loadAIInsights(),
|
||||
loadAIRecommendations()
|
||||
]);
|
||||
// Get strategy ID
|
||||
const strategyId = Number(currentStrategy?.id) || currentStrategy?.user_id || 1;
|
||||
|
||||
// Load analytics data from backend
|
||||
const response = await contentPlanningApi.getAIAnalyticsSafe();
|
||||
// First, try to load from database (monitoring data)
|
||||
try {
|
||||
console.log('🎯 Fetching analytics data from database...');
|
||||
const response = await fetch(`/api/content-planning/strategy/${strategyId}/analytics-data`);
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const dbAnalyticsData = result.data;
|
||||
|
||||
console.log('✅ Analytics data from database:', dbAnalyticsData);
|
||||
setAnalyticsData(dbAnalyticsData);
|
||||
setLastLoadTime(Date.now());
|
||||
return;
|
||||
} else {
|
||||
console.warn('⚠️ Database fetch failed, trying cache...');
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.warn('⚠️ Database fetch error, trying cache:', dbError);
|
||||
}
|
||||
|
||||
console.log('Analytics Response:', response);
|
||||
// Fallback to cached monitoring data
|
||||
const strategyAnalyticsData = localStorage.getItem('strategy_analytics_data');
|
||||
const monitoringTasks = localStorage.getItem('strategy_monitoring_tasks');
|
||||
|
||||
if (response) {
|
||||
console.log('🎯 Checking cached monitoring data...');
|
||||
|
||||
if (strategyAnalyticsData && monitoringTasks) {
|
||||
console.log('✅ Found cached monitoring data');
|
||||
|
||||
const cachedData = JSON.parse(strategyAnalyticsData);
|
||||
const tasks = JSON.parse(monitoringTasks);
|
||||
|
||||
const analyticsData = {
|
||||
performance_trends: response.performance_trends || {},
|
||||
content_evolution: response.content_evolution || {},
|
||||
engagement_patterns: response.engagement_patterns || {},
|
||||
recommendations: response.recommendations || [],
|
||||
insights: response.insights || []
|
||||
performance_trends: cachedData.monitoring_plan?.performance_metrics || {},
|
||||
content_evolution: cachedData.monitoring_plan?.content_evolution || {},
|
||||
engagement_patterns: cachedData.monitoring_plan?.engagement_patterns || {},
|
||||
recommendations: cachedData.monitoring_plan?.recommendations || [],
|
||||
insights: cachedData.monitoring_plan?.insights || [],
|
||||
monitoring_data: cachedData,
|
||||
monitoring_tasks: tasks,
|
||||
_source: 'cached_monitoring'
|
||||
};
|
||||
|
||||
console.log('Analytics Data:', analyticsData);
|
||||
console.log('✅ Analytics Data from cache:', analyticsData);
|
||||
setAnalyticsData(analyticsData);
|
||||
setLastLoadTime(Date.now());
|
||||
|
||||
} else {
|
||||
// No data available
|
||||
console.log('⚠️ No monitoring data found in database or cache');
|
||||
const emptyData = {
|
||||
performance_trends: {},
|
||||
content_evolution: {},
|
||||
engagement_patterns: {},
|
||||
recommendations: [],
|
||||
insights: [],
|
||||
monitoring_data: null,
|
||||
monitoring_tasks: [],
|
||||
_source: 'empty'
|
||||
};
|
||||
|
||||
setAnalyticsData(emptyData);
|
||||
setLastLoadTime(Date.now());
|
||||
setError('No monitoring data available. Please activate a strategy first.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading analytics data:', error);
|
||||
console.error('❌ Error loading analytics data:', error);
|
||||
setError('Failed to load analytics data. Please try again.');
|
||||
} finally {
|
||||
setDataLoading(false);
|
||||
}
|
||||
@@ -110,6 +166,12 @@ const AnalyticsTab: React.FC = () => {
|
||||
setActiveTab(newValue);
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
console.log('🔄 Manual refresh requested');
|
||||
setLastLoadTime(0); // Reset cache
|
||||
loadAnalyticsData();
|
||||
};
|
||||
|
||||
const getPerformanceColor = (value: number) => {
|
||||
if (value >= 80) return 'success';
|
||||
if (value >= 60) return 'warning';
|
||||
@@ -121,15 +183,48 @@ const AnalyticsTab: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Analytics Dashboard
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Typography variant="h4">
|
||||
Analytics Dashboard
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={handleRefresh}
|
||||
disabled={dataLoading}
|
||||
sx={{ minWidth: 120 }}
|
||||
>
|
||||
{dataLoading ? 'Refreshing...' : 'Refresh'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
{(error || storeError) && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error || storeError}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Data Source Indicator */}
|
||||
{analyticsData && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{ mb: 2 }}
|
||||
action={
|
||||
<Chip
|
||||
label={analyticsData._source === 'database_monitoring' ? 'Database' :
|
||||
analyticsData._source === 'cached_monitoring' ? 'Cache' :
|
||||
analyticsData._source === 'empty' ? 'No Data' : 'Unknown'}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
/>
|
||||
}
|
||||
>
|
||||
Data source: {analyticsData._source === 'database_monitoring' ? 'Monitoring database' :
|
||||
analyticsData._source === 'cached_monitoring' ? 'Local cache' :
|
||||
analyticsData._source === 'empty' ? 'No monitoring data available' : 'Unknown source'}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Tabs Navigation */}
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
||||
@@ -191,38 +286,38 @@ const AnalyticsTab: React.FC = () => {
|
||||
</Typography>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
{performanceMetrics ? (
|
||||
{analyticsData && analyticsData.performance_trends ? (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Engagement Rate
|
||||
</Typography>
|
||||
<Typography variant="h4" color={getPerformanceColor(performanceMetrics.engagement)}>
|
||||
{performanceMetrics.engagement}%
|
||||
<Typography variant="h4" color={getPerformanceColor(analyticsData.performance_trends.engagement_rate || 0)}>
|
||||
{analyticsData.performance_trends.engagement_rate || 0}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Reach
|
||||
Traffic Growth
|
||||
</Typography>
|
||||
<Typography variant="h4" color="primary">
|
||||
{performanceMetrics.reach.toLocaleString()}
|
||||
{analyticsData.performance_trends.traffic_growth || 0}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Conversion Rate
|
||||
</Typography>
|
||||
<Typography variant="h4" color={getPerformanceColor(performanceMetrics.conversion)}>
|
||||
{performanceMetrics.conversion}%
|
||||
<Typography variant="h4" color={getPerformanceColor(analyticsData.performance_trends.conversion_rate || 0)}>
|
||||
{analyticsData.performance_trends.conversion_rate || 0}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
ROI
|
||||
Content Quality
|
||||
</Typography>
|
||||
<Typography variant="h4" color="success.main">
|
||||
${performanceMetrics.roi.toLocaleString()}
|
||||
{analyticsData.performance_trends.content_quality_score || 0}/100
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -243,18 +338,18 @@ const AnalyticsTab: React.FC = () => {
|
||||
</Typography>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
{aiInsights && aiInsights.length > 0 ? (
|
||||
{analyticsData && analyticsData.insights && analyticsData.insights.length > 0 ? (
|
||||
<Box>
|
||||
{aiInsights.slice(0, 3).map((insight, index) => (
|
||||
{analyticsData.insights.slice(0, 3).map((insight: any, index: number) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{insight.title}
|
||||
{insight.title || `Insight ${index + 1}`}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
{insight.description}
|
||||
{insight.description || insight}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={insight.priority}
|
||||
label={insight.priority || 'medium'}
|
||||
color={insight.priority === 'high' ? 'error' : insight.priority === 'medium' ? 'warning' : 'success'}
|
||||
size="small"
|
||||
/>
|
||||
@@ -263,7 +358,7 @@ const AnalyticsTab: React.FC = () => {
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No AI insights available
|
||||
No insights available
|
||||
</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
Reference in New Issue
Block a user