Files
ALwrity/frontend/src/services/contentPlanningOrchestrator.ts
2025-08-09 23:14:16 +05:30

431 lines
13 KiB
TypeScript

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;
private latestDashboardData: DashboardData | null = null;
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;
}
});
this.latestDashboardData = dashboardData;
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...'
});
// New approach: stream strategic intelligence data and show status from AI generation SSE
return await new Promise<{ aiInsights: any[]; aiRecommendations: any[] }>(async (resolve) => {
// 1) Execution status stream (best-effort; ignore if no active strategy)
try {
const currentStrategyId = this.latestDashboardData?.strategies?.[0]?.id;
if (currentStrategyId) {
const statusSource = await contentPlanningApi.streamAIGenerationStatus(currentStrategyId);
statusSource.onmessage = (event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'progress') {
this.updateServiceStatus('aiAnalytics', {
status: 'loading',
progress: Math.min(99, data.progress || 20),
message: data.detail || 'AI generation in progress...'
});
}
if (data.type === 'result') {
this.updateServiceStatus('aiAnalytics', {
status: data.status === 'completed' ? 'success' : 'error',
progress: 100,
message: data.status === 'completed' ? 'AI generation completed' : 'AI generation failed'
});
statusSource.close();
}
} catch {}
};
statusSource.onerror = () => statusSource.close();
}
} catch {}
// 2) Data stream for insights (Strategic Intelligence)
const intelSource = await contentPlanningApi.streamStrategicIntelligence(1);
contentPlanningApi.handleSSEData(
intelSource,
(data) => {
if (data.type === 'progress') {
this.updateServiceStatus('aiAnalytics', {
status: 'loading',
progress: Math.max(20, data.progress || 40),
message: data.message || 'Analyzing strategic intelligence...'
});
} else if (data.type === 'result' && data.status === 'success') {
this.updateServiceStatus('aiAnalytics', {
status: 'success',
progress: 100,
message: 'Strategic intelligence ready',
data: data.data
});
// Map to orchestrator fields if needed
this.notifyDataUpdate({ aiInsights: data.data?.recommendations || [], aiRecommendations: [] });
resolve({ aiInsights: data.data?.recommendations || [], aiRecommendations: [] });
} else if (data.type === 'error') {
this.updateServiceStatus('aiAnalytics', {
status: 'error',
progress: 0,
message: data.message || 'Failed to load strategic intelligence'
});
resolve({ aiInsights: [], aiRecommendations: [] });
}
},
() => {
resolve({ aiInsights: [], aiRecommendations: [] });
}
);
});
} catch (error: any) {
this.updateServiceStatus('aiAnalytics', {
status: 'error',
progress: 0,
message: 'Failed to load AI analytics',
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();