ALwrity Version 0.5.0 (Fastapi + React )
This commit is contained in:
690
frontend/src/services/contentPlanningApi.ts
Normal file
690
frontend/src/services/contentPlanningApi.ts
Normal file
@@ -0,0 +1,690 @@
|
||||
import { apiClient, aiApiClient } from '../api/client';
|
||||
|
||||
// Types
|
||||
export interface ContentStrategyCreate {
|
||||
name: string;
|
||||
description: string;
|
||||
industry: string;
|
||||
target_audience: string;
|
||||
content_pillars: string[];
|
||||
user_id?: number;
|
||||
}
|
||||
|
||||
export interface ContentStrategyUpdate {
|
||||
name?: string;
|
||||
description?: string;
|
||||
industry?: string;
|
||||
target_audience?: string;
|
||||
content_pillars?: string[];
|
||||
}
|
||||
|
||||
export interface CalendarEventCreate {
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
platform: string;
|
||||
content_type: string;
|
||||
status: 'draft' | 'scheduled' | 'published';
|
||||
strategy_id?: string;
|
||||
user_id?: number;
|
||||
}
|
||||
|
||||
export interface CalendarEventUpdate {
|
||||
title?: string;
|
||||
description?: string;
|
||||
date?: string;
|
||||
platform?: string;
|
||||
content_type?: string;
|
||||
status?: 'draft' | 'scheduled' | 'published';
|
||||
strategy_id?: string;
|
||||
}
|
||||
|
||||
export interface GapAnalysisCreate {
|
||||
website_url: string;
|
||||
competitors: string[];
|
||||
keywords: string[];
|
||||
user_id?: number;
|
||||
}
|
||||
|
||||
export interface GapAnalysisUpdate {
|
||||
website_url?: string;
|
||||
competitors?: string[];
|
||||
keywords?: string[];
|
||||
gaps?: string[];
|
||||
recommendations?: any[];
|
||||
}
|
||||
|
||||
export interface AIAnalyticsCreate {
|
||||
analysis_type: string;
|
||||
data: any;
|
||||
insights: any[];
|
||||
user_id?: number;
|
||||
}
|
||||
|
||||
export interface AIAnalyticsUpdate {
|
||||
analysis_type?: string;
|
||||
data?: any;
|
||||
insights?: any[];
|
||||
}
|
||||
|
||||
// New Calendar Generation Interfaces
|
||||
export interface CalendarGenerationRequest {
|
||||
user_id: number;
|
||||
strategy_id?: number;
|
||||
calendar_type: string;
|
||||
industry?: string;
|
||||
business_size: string;
|
||||
force_refresh?: boolean;
|
||||
}
|
||||
|
||||
export interface CalendarGenerationResponse {
|
||||
user_id: number;
|
||||
strategy_id?: number;
|
||||
calendar_type: string;
|
||||
industry: string;
|
||||
business_size: string;
|
||||
generated_at: string;
|
||||
content_pillars: string[];
|
||||
platform_strategies: any;
|
||||
content_mix: Record<string, number>;
|
||||
daily_schedule: any[];
|
||||
weekly_themes: any[];
|
||||
content_recommendations: any[];
|
||||
optimal_timing: any;
|
||||
performance_predictions: any;
|
||||
trending_topics: any[];
|
||||
repurposing_opportunities: any[];
|
||||
ai_insights: any[];
|
||||
competitor_analysis: any;
|
||||
gap_analysis_insights: any;
|
||||
strategy_insights: any;
|
||||
onboarding_insights: any;
|
||||
processing_time: number;
|
||||
ai_confidence: number;
|
||||
}
|
||||
|
||||
export interface ContentOptimizationRequest {
|
||||
user_id: number;
|
||||
event_id?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
content_type: string;
|
||||
target_platform: string;
|
||||
original_content?: any;
|
||||
}
|
||||
|
||||
export interface ContentOptimizationResponse {
|
||||
user_id: number;
|
||||
event_id?: number;
|
||||
original_content: any;
|
||||
optimized_content: any;
|
||||
platform_adaptations: string[];
|
||||
visual_recommendations: string[];
|
||||
hashtag_suggestions: string[];
|
||||
keyword_optimization: any;
|
||||
tone_adjustments: any;
|
||||
length_optimization: any;
|
||||
performance_prediction: any;
|
||||
optimization_score: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface PerformancePredictionRequest {
|
||||
user_id: number;
|
||||
strategy_id?: number;
|
||||
content_type: string;
|
||||
platform: string;
|
||||
content_data: any;
|
||||
}
|
||||
|
||||
export interface PerformancePredictionResponse {
|
||||
user_id: number;
|
||||
strategy_id?: number;
|
||||
content_type: string;
|
||||
platform: string;
|
||||
predicted_engagement_rate: number;
|
||||
predicted_reach: number;
|
||||
predicted_conversions: number;
|
||||
predicted_roi: number;
|
||||
confidence_score: number;
|
||||
recommendations: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface ContentRepurposingRequest {
|
||||
user_id: number;
|
||||
strategy_id?: number;
|
||||
original_content: any;
|
||||
target_platforms: string[];
|
||||
}
|
||||
|
||||
export interface ContentRepurposingResponse {
|
||||
user_id: number;
|
||||
strategy_id?: number;
|
||||
original_content: any;
|
||||
platform_adaptations: any[];
|
||||
transformations: any[];
|
||||
implementation_tips: string[];
|
||||
gap_addresses: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface TrendingTopicsRequest {
|
||||
user_id: number;
|
||||
industry: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface TrendingTopicsResponse {
|
||||
user_id: number;
|
||||
industry: string;
|
||||
trending_topics: any[];
|
||||
gap_relevance_scores: Record<string, number>;
|
||||
audience_alignment_scores: Record<string, number>;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// Content Planning API Service
|
||||
class ContentPlanningAPI {
|
||||
private baseURL = '/api/content-planning';
|
||||
|
||||
// Content Strategy APIs
|
||||
async createStrategy(strategy: ContentStrategyCreate) {
|
||||
const response = await apiClient.post(`${this.baseURL}/strategies/`, strategy);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getStrategies(userId?: number) {
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
const response = await apiClient.get(`${this.baseURL}/strategies/`, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getStrategy(id: string) {
|
||||
const response = await apiClient.get(`${this.baseURL}/strategies/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async updateStrategy(id: string, updates: ContentStrategyUpdate) {
|
||||
const response = await apiClient.put(`${this.baseURL}/strategies/${id}`, updates);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async deleteStrategy(id: string) {
|
||||
const response = await apiClient.delete(`${this.baseURL}/strategies/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Calendar Event APIs
|
||||
async createEvent(event: CalendarEventCreate) {
|
||||
const response = await apiClient.post(`${this.baseURL}/calendar-events/`, event);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getEvents(userId?: number, filters?: any) {
|
||||
const params = { ...filters };
|
||||
if (userId) params.user_id = userId;
|
||||
const response = await apiClient.get(`${this.baseURL}/calendar-events/`, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getEvent(id: string) {
|
||||
const response = await apiClient.get(`${this.baseURL}/calendar-events/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async updateEvent(id: string, updates: CalendarEventUpdate) {
|
||||
const response = await apiClient.put(`${this.baseURL}/calendar-events/${id}`, updates);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async deleteEvent(id: string) {
|
||||
const response = await apiClient.delete(`${this.baseURL}/calendar-events/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Gap Analysis APIs
|
||||
async createGapAnalysis(analysis: GapAnalysisCreate) {
|
||||
const response = await apiClient.post(`${this.baseURL}/gap-analysis/`, analysis);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getGapAnalyses(userId?: number) {
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
const response = await apiClient.get(`${this.baseURL}/gap-analysis/`, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getGapAnalysis(id: string) {
|
||||
const response = await apiClient.get(`${this.baseURL}/gap-analysis/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async updateGapAnalysis(id: string, updates: GapAnalysisUpdate) {
|
||||
const response = await apiClient.put(`${this.baseURL}/gap-analysis/${id}`, updates);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async deleteGapAnalysis(id: string) {
|
||||
const response = await apiClient.delete(`${this.baseURL}/gap-analysis/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// AI-Powered Gap Analysis - Using AI client for longer timeout
|
||||
async analyzeContentGaps(params: {
|
||||
website_url: string;
|
||||
competitors: string[];
|
||||
keywords: string[];
|
||||
user_id?: number;
|
||||
}) {
|
||||
const response = await aiApiClient.post(`${this.baseURL}/gap-analysis/analyze`, params);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// AI Analytics APIs - Using AI client for longer timeout
|
||||
async createAIAnalytics(analytics: AIAnalyticsCreate) {
|
||||
const response = await aiApiClient.post(`${this.baseURL}/ai-analytics/`, analytics);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getAIAnalytics(userId?: number) {
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
const response = await aiApiClient.get(`${this.baseURL}/ai-analytics/`, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getAIAnalyticsById(id: string) {
|
||||
const response = await aiApiClient.get(`${this.baseURL}/ai-analytics/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async updateAIAnalytics(id: string, updates: AIAnalyticsUpdate) {
|
||||
const response = await aiApiClient.put(`${this.baseURL}/ai-analytics/${id}`, updates);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async deleteAIAnalytics(id: string) {
|
||||
const response = await aiApiClient.delete(`${this.baseURL}/ai-analytics/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// AI Analytics with Server-Sent Events
|
||||
async streamAIAnalytics(
|
||||
onProgress: (data: any) => void,
|
||||
onComplete: (data: any) => void,
|
||||
onError: (error: any) => void,
|
||||
userId?: number
|
||||
) {
|
||||
try {
|
||||
const params: Record<string, string> = {};
|
||||
if (userId) {
|
||||
params.user_id = userId.toString();
|
||||
}
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
const url = `${this.baseURL}/ai-analytics/stream?${queryString}`;
|
||||
|
||||
const eventSource = new EventSource(url);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
switch (data.type) {
|
||||
case 'connected':
|
||||
onProgress({ message: data.message, progress: 0 });
|
||||
break;
|
||||
case 'progress':
|
||||
onProgress({
|
||||
message: data.message,
|
||||
progress: data.progress,
|
||||
step: data.step
|
||||
});
|
||||
break;
|
||||
case 'complete':
|
||||
onComplete(data);
|
||||
eventSource.close();
|
||||
break;
|
||||
case 'error':
|
||||
onError(new Error(data.message));
|
||||
eventSource.close();
|
||||
break;
|
||||
}
|
||||
} catch (parseError) {
|
||||
onError(new Error('Failed to parse server message'));
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
onError(new Error('EventSource failed'));
|
||||
eventSource.close();
|
||||
};
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
} catch (error: any) {
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Health Check APIs
|
||||
async checkHealth() {
|
||||
const response = await apiClient.get(`${this.baseURL}/health`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async checkBackendHealth() {
|
||||
const response = await apiClient.get(`${this.baseURL}/health/backend`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async checkAIHealth() {
|
||||
const response = await apiClient.get(`${this.baseURL}/health/ai`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async checkDatabaseHealth() {
|
||||
const response = await apiClient.get(`${this.baseURL}/database/health`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Error handling wrapper with AI-specific error messages
|
||||
private async handleRequest<T>(request: () => Promise<T>, isAI: boolean = false): Promise<T> {
|
||||
try {
|
||||
return await request();
|
||||
} catch (error: any) {
|
||||
console.error('API Error:', error);
|
||||
|
||||
if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
|
||||
if (isAI) {
|
||||
throw new Error('AI analysis is taking longer than expected. This is normal for complex AI operations. Please wait a moment and try again.');
|
||||
} else {
|
||||
throw new Error('Request timed out. Please check your connection and try again.');
|
||||
}
|
||||
} else if (error.response) {
|
||||
// Server responded with error status
|
||||
const message = error.response.data?.detail || error.response.data?.message || 'API request failed';
|
||||
throw new Error(message);
|
||||
} else if (error.request) {
|
||||
// Request was made but no response received
|
||||
if (isAI) {
|
||||
throw new Error('AI service is not responding. The AI analysis may be in progress. Please wait and try again.');
|
||||
} else {
|
||||
throw new Error('No response from server. Please check your connection.');
|
||||
}
|
||||
} else {
|
||||
// Something else happened
|
||||
throw new Error('An unexpected error occurred.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapped methods with error handling
|
||||
async createStrategySafe(strategy: ContentStrategyCreate) {
|
||||
return this.handleRequest(() => this.createStrategy(strategy));
|
||||
}
|
||||
|
||||
async getStrategiesSafe(userId?: number) {
|
||||
return this.handleRequest(() => this.getStrategies(userId));
|
||||
}
|
||||
|
||||
async createEventSafe(event: CalendarEventCreate) {
|
||||
return this.handleRequest(() => this.createEvent(event));
|
||||
}
|
||||
|
||||
async getEventsSafe(userId?: number, filters?: any) {
|
||||
return this.handleRequest(() => this.getEvents(userId, filters));
|
||||
}
|
||||
|
||||
async createGapAnalysisSafe(analysis: GapAnalysisCreate) {
|
||||
return this.handleRequest(() => this.createGapAnalysis(analysis));
|
||||
}
|
||||
|
||||
async getGapAnalysesSafe(userId?: number) {
|
||||
return this.handleRequest(() => this.getGapAnalyses(userId));
|
||||
}
|
||||
|
||||
async analyzeContentGapsSafe(params: {
|
||||
website_url: string;
|
||||
competitors: string[];
|
||||
keywords: string[];
|
||||
user_id?: number;
|
||||
}) {
|
||||
return this.handleRequest(() => this.analyzeContentGaps(params), true);
|
||||
}
|
||||
|
||||
async getAIAnalyticsSafe(userId?: number) {
|
||||
return this.handleRequest(() => this.getAIAnalytics(userId), true);
|
||||
}
|
||||
|
||||
// AI Analytics with force refresh option
|
||||
async getAIAnalyticsWithRefresh(userId?: number, forceRefresh = false): Promise<any> {
|
||||
try {
|
||||
const params: any = { user_id: userId || 1 };
|
||||
if (forceRefresh) {
|
||||
params.force_refresh = true;
|
||||
}
|
||||
const response = await apiClient.get(`${this.baseURL}/ai-analytics/`, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error getting AI analytics with refresh:', error);
|
||||
return { insights: [], recommendations: [], total_insights: 0, total_recommendations: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
async getGapAnalysesWithRefresh(userId?: number, forceRefresh = false): Promise<any> {
|
||||
try {
|
||||
const params: any = { user_id: userId || 1 };
|
||||
if (forceRefresh) {
|
||||
params.force_refresh = true;
|
||||
}
|
||||
const response = await apiClient.get(`${this.baseURL}/gap-analysis/`, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error getting gap analyses with refresh:', error);
|
||||
return { gap_analyses: [], total_gaps: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// New Calendar Generation APIs
|
||||
async generateCalendar(request: CalendarGenerationRequest): Promise<CalendarGenerationResponse> {
|
||||
const response = await apiClient.post(`${this.baseURL}/generate-calendar`, request);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async optimizeContent(request: ContentOptimizationRequest): Promise<ContentOptimizationResponse> {
|
||||
const response = await apiClient.post(`${this.baseURL}/optimize-content`, request);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async predictPerformance(request: PerformancePredictionRequest): Promise<PerformancePredictionResponse> {
|
||||
const response = await apiClient.post(`${this.baseURL}/performance-predictions`, request);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async repurposeContent(request: ContentRepurposingRequest): Promise<ContentRepurposingResponse> {
|
||||
const response = await apiClient.post(`${this.baseURL}/repurpose-content`, request);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getTrendingTopics(request: TrendingTopicsRequest): Promise<TrendingTopicsResponse> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.post(`${this.baseURL}/trending-topics`, request);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getComprehensiveUserData(userId?: number): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/comprehensive-user-data`, {
|
||||
params: { user_id: userId }
|
||||
});
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async generateComprehensiveCalendar(config: any): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.post(`${this.baseURL}/generate-comprehensive-calendar`, config);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async checkCalendarGenerationHealth(): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/calendar-generation/health`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
// Enhanced Strategy API Methods
|
||||
async createEnhancedStrategy(strategy: any): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.post(`${this.baseURL}/enhanced-strategies/create`, strategy);
|
||||
// Extract data from the response wrapper
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateEnhancedStrategy(id: string, updates: any): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.put(`${this.baseURL}/enhanced-strategies/${id}`, updates);
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEnhancedStrategy(id: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.delete(`${this.baseURL}/enhanced-strategies/${id}`);
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getEnhancedStrategies(userId?: number): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies`, { params });
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getEnhancedStrategy(id: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/${id}`);
|
||||
return response.data.data || response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async generateEnhancedAIRecommendations(strategyId: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.post(`${this.baseURL}/enhanced-strategies/${strategyId}/ai-recommendations`);
|
||||
return response.data.data || response.data;
|
||||
}, true);
|
||||
}
|
||||
|
||||
async regenerateAIAnalysis(strategyId: string, analysisType: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.post(`${this.baseURL}/enhanced-strategies/${strategyId}/ai-analysis/regenerate`, {
|
||||
analysis_type: analysisType
|
||||
});
|
||||
return response.data;
|
||||
}, true);
|
||||
}
|
||||
|
||||
async getEnhancedAIAnalyses(strategyId: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/${strategyId}/ai-analyses`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getOnboardingData(userId?: number): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/onboarding-data`, { params });
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getOnboardingIntegration(strategyId: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/${strategyId}/onboarding-integration`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getEnhancedStrategyAnalytics(strategyId: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/${strategyId}/analytics`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getEnhancedStrategyCompletion(strategyId: string): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/${strategyId}/completion`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getEnhancedStrategyTooltips(): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/tooltips`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getEnhancedStrategyDisclosureSteps(): Promise<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.get(`${this.baseURL}/enhanced-strategies/disclosure-steps`);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
// Enhanced Strategy Streaming Methods
|
||||
async streamEnhancedStrategies(userId?: number): Promise<EventSource> {
|
||||
const url = `${this.baseURL}/enhanced-strategies/stream/strategies?user_id=${userId || 1}`;
|
||||
return new EventSource(url);
|
||||
}
|
||||
|
||||
async streamStrategicIntelligence(userId?: number): Promise<EventSource> {
|
||||
const url = `${this.baseURL}/enhanced-strategies/stream/strategic-intelligence?user_id=${userId || 1}`;
|
||||
return new EventSource(url);
|
||||
}
|
||||
|
||||
async streamKeywordResearch(userId?: number): Promise<EventSource> {
|
||||
const url = `${this.baseURL}/enhanced-strategies/stream/keyword-research?user_id=${userId || 1}`;
|
||||
return new EventSource(url);
|
||||
}
|
||||
|
||||
// Helper method to handle SSE data
|
||||
handleSSEData(eventSource: EventSource, onData: (data: any) => void, onError?: (error: any) => void, onComplete?: () => void) {
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
onData(data);
|
||||
|
||||
// Close connection when we get a result or error
|
||||
if (data.type === 'result' || data.type === 'error') {
|
||||
eventSource.close();
|
||||
onComplete?.();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing SSE data:', error);
|
||||
onError?.(error);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('SSE Error:', error);
|
||||
onError?.(error);
|
||||
eventSource.close();
|
||||
};
|
||||
|
||||
return eventSource;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const contentPlanningApi = new ContentPlanningAPI();
|
||||
403
frontend/src/services/contentPlanningOrchestrator.ts
Normal file
403
frontend/src/services/contentPlanningOrchestrator.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
import { contentPlanningApi } from './contentPlanningApi';
|
||||
|
||||
export interface ServiceStatus {
|
||||
name: string;
|
||||
status: 'loading' | 'success' | 'error' | 'idle';
|
||||
progress: number;
|
||||
message: string;
|
||||
data?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
strategies: any[];
|
||||
gapAnalyses: any[];
|
||||
aiInsights: any[];
|
||||
aiRecommendations: any[];
|
||||
calendarEvents: any[];
|
||||
healthStatus: {
|
||||
backend: boolean;
|
||||
database: boolean;
|
||||
aiServices: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export class ContentPlanningOrchestrator {
|
||||
private serviceStatuses: Map<string, ServiceStatus> = new Map();
|
||||
private onProgressUpdate?: (statuses: ServiceStatus[]) => void;
|
||||
private onDataUpdate?: (data: Partial<DashboardData>) => void;
|
||||
|
||||
constructor() {
|
||||
this.initializeServiceStatuses();
|
||||
}
|
||||
|
||||
private initializeServiceStatuses() {
|
||||
const services = [
|
||||
{ name: 'strategies', displayName: 'Content Strategies' },
|
||||
{ name: 'gapAnalyses', displayName: 'Gap Analysis' },
|
||||
{ name: 'aiAnalytics', displayName: 'AI Analytics' },
|
||||
{ name: 'calendarEvents', displayName: 'Calendar Events' },
|
||||
{ name: 'healthCheck', displayName: 'System Health' }
|
||||
];
|
||||
|
||||
services.forEach(service => {
|
||||
this.serviceStatuses.set(service.name, {
|
||||
name: service.displayName,
|
||||
status: 'idle',
|
||||
progress: 0,
|
||||
message: 'Ready to load'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public setProgressCallback(callback: (statuses: ServiceStatus[]) => void) {
|
||||
this.onProgressUpdate = callback;
|
||||
}
|
||||
|
||||
public setDataUpdateCallback(callback: (data: Partial<DashboardData>) => void) {
|
||||
this.onDataUpdate = callback;
|
||||
}
|
||||
|
||||
private updateServiceStatus(name: string, updates: Partial<ServiceStatus>) {
|
||||
const current = this.serviceStatuses.get(name);
|
||||
if (current) {
|
||||
const updated = { ...current, ...updates };
|
||||
this.serviceStatuses.set(name, updated);
|
||||
this.notifyProgressUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private notifyProgressUpdate() {
|
||||
if (this.onProgressUpdate) {
|
||||
this.onProgressUpdate(Array.from(this.serviceStatuses.values()));
|
||||
}
|
||||
}
|
||||
|
||||
private notifyDataUpdate(data: Partial<DashboardData>) {
|
||||
if (this.onDataUpdate) {
|
||||
this.onDataUpdate(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async loadDashboardData(): Promise<DashboardData> {
|
||||
// Reset all service statuses
|
||||
this.serviceStatuses.forEach((status, name) => {
|
||||
this.updateServiceStatus(name, {
|
||||
status: 'loading',
|
||||
progress: 0,
|
||||
message: 'Initializing...'
|
||||
});
|
||||
});
|
||||
|
||||
// Start parallel requests
|
||||
const promises = [
|
||||
this.loadStrategies(),
|
||||
this.loadGapAnalyses(),
|
||||
this.loadAIAnalytics(),
|
||||
this.loadCalendarEvents(),
|
||||
this.loadHealthStatus()
|
||||
];
|
||||
|
||||
// Wait for all to complete but handle each independently
|
||||
const results = await Promise.allSettled(promises);
|
||||
|
||||
// Compile final data
|
||||
const dashboardData: DashboardData = {
|
||||
strategies: [],
|
||||
gapAnalyses: [],
|
||||
aiInsights: [],
|
||||
aiRecommendations: [],
|
||||
calendarEvents: [],
|
||||
healthStatus: {
|
||||
backend: false,
|
||||
database: false,
|
||||
aiServices: false
|
||||
}
|
||||
};
|
||||
|
||||
results.forEach((result) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
const data = result.value;
|
||||
// Type-safe data assignment
|
||||
if ('strategies' in data) dashboardData.strategies = data.strategies;
|
||||
if ('gapAnalyses' in data) dashboardData.gapAnalyses = data.gapAnalyses;
|
||||
if ('aiInsights' in data) dashboardData.aiInsights = data.aiInsights;
|
||||
if ('aiRecommendations' in data) dashboardData.aiRecommendations = data.aiRecommendations;
|
||||
if ('calendarEvents' in data) dashboardData.calendarEvents = data.calendarEvents;
|
||||
if ('healthStatus' in data) dashboardData.healthStatus = data.healthStatus;
|
||||
}
|
||||
});
|
||||
|
||||
return dashboardData;
|
||||
}
|
||||
|
||||
private async loadStrategies() {
|
||||
try {
|
||||
this.updateServiceStatus('strategies', {
|
||||
status: 'loading',
|
||||
progress: 10,
|
||||
message: 'Loading content strategies...'
|
||||
});
|
||||
|
||||
const strategies = await contentPlanningApi.getStrategiesSafe();
|
||||
|
||||
this.updateServiceStatus('strategies', {
|
||||
status: 'loading',
|
||||
progress: 50,
|
||||
message: 'Processing strategy data...'
|
||||
});
|
||||
|
||||
// Simulate processing time for better UX
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
this.updateServiceStatus('strategies', {
|
||||
status: 'success',
|
||||
progress: 100,
|
||||
message: `Loaded ${strategies.length} content strategies`,
|
||||
data: strategies
|
||||
});
|
||||
|
||||
this.notifyDataUpdate({ strategies });
|
||||
|
||||
return { strategies };
|
||||
} catch (error: any) {
|
||||
this.updateServiceStatus('strategies', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'Failed to load strategies',
|
||||
error: error.message
|
||||
});
|
||||
return { strategies: [] };
|
||||
}
|
||||
}
|
||||
|
||||
private async loadGapAnalyses() {
|
||||
try {
|
||||
this.updateServiceStatus('gapAnalyses', {
|
||||
status: 'loading',
|
||||
progress: 10,
|
||||
message: 'Initializing gap analysis...'
|
||||
});
|
||||
|
||||
const response = await contentPlanningApi.getGapAnalysesSafe();
|
||||
|
||||
this.updateServiceStatus('gapAnalyses', {
|
||||
status: 'loading',
|
||||
progress: 30,
|
||||
message: 'Analyzing content gaps...'
|
||||
});
|
||||
|
||||
// Simulate processing time
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
this.updateServiceStatus('gapAnalyses', {
|
||||
status: 'loading',
|
||||
progress: 70,
|
||||
message: 'Processing gap analysis results...'
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
this.updateServiceStatus('gapAnalyses', {
|
||||
status: 'success',
|
||||
progress: 100,
|
||||
message: `Found ${response.gap_analyses?.length || 0} content gaps`,
|
||||
data: response
|
||||
});
|
||||
|
||||
this.notifyDataUpdate({ gapAnalyses: response.gap_analyses || [] });
|
||||
|
||||
return { gapAnalyses: response.gap_analyses || [] };
|
||||
} catch (error: any) {
|
||||
this.updateServiceStatus('gapAnalyses', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'Failed to load gap analysis',
|
||||
error: error.message
|
||||
});
|
||||
return { gapAnalyses: [] };
|
||||
}
|
||||
}
|
||||
|
||||
private async loadAIAnalytics() {
|
||||
try {
|
||||
this.updateServiceStatus('aiAnalytics', {
|
||||
status: 'loading',
|
||||
progress: 10,
|
||||
message: 'Initializing AI analysis...'
|
||||
});
|
||||
|
||||
return new Promise<{ aiInsights: any[]; aiRecommendations: any[] }>((resolve, reject) => {
|
||||
contentPlanningApi.streamAIAnalytics(
|
||||
// Progress callback
|
||||
(progressData) => {
|
||||
this.updateServiceStatus('aiAnalytics', {
|
||||
progress: progressData.progress,
|
||||
message: progressData.message || 'AI analysis in progress...'
|
||||
});
|
||||
},
|
||||
// Complete callback
|
||||
(aiData) => {
|
||||
this.updateServiceStatus('aiAnalytics', {
|
||||
status: 'success',
|
||||
progress: 100,
|
||||
message: `Generated ${aiData.insights?.length || 0} insights and ${aiData.recommendations?.length || 0} recommendations`,
|
||||
data: aiData
|
||||
});
|
||||
|
||||
this.notifyDataUpdate({
|
||||
aiInsights: aiData.insights || [],
|
||||
aiRecommendations: aiData.recommendations || []
|
||||
});
|
||||
|
||||
resolve({
|
||||
aiInsights: aiData.insights || [],
|
||||
aiRecommendations: aiData.recommendations || []
|
||||
});
|
||||
},
|
||||
// Error callback
|
||||
(error) => {
|
||||
this.updateServiceStatus('aiAnalytics', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'AI analysis failed',
|
||||
error: error.message
|
||||
});
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error: any) {
|
||||
this.updateServiceStatus('aiAnalytics', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'AI analysis failed',
|
||||
error: error.message
|
||||
});
|
||||
return { aiInsights: [], aiRecommendations: [] };
|
||||
}
|
||||
}
|
||||
|
||||
private async loadCalendarEvents() {
|
||||
try {
|
||||
this.updateServiceStatus('calendarEvents', {
|
||||
status: 'loading',
|
||||
progress: 10,
|
||||
message: 'Loading calendar events...'
|
||||
});
|
||||
|
||||
const calendarEvents = await contentPlanningApi.getEventsSafe();
|
||||
|
||||
this.updateServiceStatus('calendarEvents', {
|
||||
status: 'loading',
|
||||
progress: 50,
|
||||
message: 'Processing calendar data...'
|
||||
});
|
||||
|
||||
// Simulate processing time
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
this.updateServiceStatus('calendarEvents', {
|
||||
status: 'success',
|
||||
progress: 100,
|
||||
message: `Loaded ${calendarEvents.length} calendar events`,
|
||||
data: calendarEvents
|
||||
});
|
||||
|
||||
this.notifyDataUpdate({ calendarEvents });
|
||||
|
||||
return { calendarEvents };
|
||||
} catch (error: any) {
|
||||
this.updateServiceStatus('calendarEvents', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'Failed to load calendar events',
|
||||
error: error.message
|
||||
});
|
||||
return { calendarEvents: [] };
|
||||
}
|
||||
}
|
||||
|
||||
private async loadHealthStatus() {
|
||||
try {
|
||||
this.updateServiceStatus('healthCheck', {
|
||||
status: 'loading',
|
||||
progress: 25,
|
||||
message: 'Checking system health...'
|
||||
});
|
||||
|
||||
const [backendHealth, aiHealth] = await Promise.allSettled([
|
||||
contentPlanningApi.checkBackendHealth(),
|
||||
contentPlanningApi.checkAIHealth()
|
||||
]);
|
||||
|
||||
const healthStatus = {
|
||||
backend: backendHealth.status === 'fulfilled' && backendHealth.value.status === 'healthy',
|
||||
database: backendHealth.status === 'fulfilled' && backendHealth.value.services?.database_connection === true,
|
||||
aiServices: aiHealth.status === 'fulfilled' && aiHealth.value.status === 'healthy'
|
||||
};
|
||||
|
||||
this.updateServiceStatus('healthCheck', {
|
||||
status: 'success',
|
||||
progress: 100,
|
||||
message: 'System health check complete',
|
||||
data: healthStatus
|
||||
});
|
||||
|
||||
this.notifyDataUpdate({ healthStatus });
|
||||
|
||||
return { healthStatus };
|
||||
} catch (error: any) {
|
||||
this.updateServiceStatus('healthCheck', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'Health check failed',
|
||||
error: error.message
|
||||
});
|
||||
return {
|
||||
healthStatus: {
|
||||
backend: false,
|
||||
database: false,
|
||||
aiServices: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public getServiceStatuses(): ServiceStatus[] {
|
||||
return Array.from(this.serviceStatuses.values());
|
||||
}
|
||||
|
||||
public refreshService(serviceName: string) {
|
||||
const status = this.serviceStatuses.get(serviceName);
|
||||
if (status) {
|
||||
this.updateServiceStatus(serviceName, {
|
||||
status: 'loading',
|
||||
progress: 0,
|
||||
message: 'Refreshing...'
|
||||
});
|
||||
|
||||
// Re-run the specific service
|
||||
switch (serviceName) {
|
||||
case 'strategies':
|
||||
this.loadStrategies();
|
||||
break;
|
||||
case 'gapAnalyses':
|
||||
this.loadGapAnalyses();
|
||||
break;
|
||||
case 'aiAnalytics':
|
||||
this.loadAIAnalytics();
|
||||
break;
|
||||
case 'calendarEvents':
|
||||
this.loadCalendarEvents();
|
||||
break;
|
||||
case 'healthCheck':
|
||||
this.loadHealthStatus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const contentPlanningOrchestrator = new ContentPlanningOrchestrator();
|
||||
Reference in New Issue
Block a user