Files
ALwrity/frontend/src/services/TaskWorkflowOrchestrator.ts

612 lines
19 KiB
TypeScript

import {
TodayTask,
DailyWorkflow,
WorkflowProgress,
TaskCompletionData,
TaskGenerationContext,
WorkflowOrchestratorConfig,
NavigationState,
WorkflowError
} from '../types/workflow';
import { taskNavigationService } from './TaskNavigationService';
import { taskDependencyManager } from './TaskDependencyManager';
import { taskCompletionVerifier } from './TaskCompletionVerifier';
class TaskWorkflowOrchestrator {
private workflows: Map<string, DailyWorkflow> = new Map();
private config: WorkflowOrchestratorConfig;
constructor(config: WorkflowOrchestratorConfig = {
autoNavigate: true,
showProgress: true,
enableNotifications: true,
persistProgress: true,
allowTaskSkipping: true
}) {
this.config = config;
this.loadPersistedWorkflows();
}
/**
* Generate a new daily workflow for a user
*/
async generateDailyWorkflow(
userId: string,
date: string = new Date().toISOString().split('T')[0],
context?: TaskGenerationContext
): Promise<DailyWorkflow> {
try {
// Check if workflow already exists for this date
const existingWorkflow = this.getWorkflow(userId, date);
if (existingWorkflow) {
return existingWorkflow;
}
// Generate tasks based on context or default configuration
const tasks = await this.generateTasksForDate(userId, date, context);
// Create new workflow
const workflow: DailyWorkflow = {
id: `${userId}-${date}`,
date,
userId,
tasks,
currentTaskIndex: 0,
completedTasks: 0,
totalTasks: tasks.length,
workflowStatus: 'not_started',
totalEstimatedTime: tasks.reduce((sum, task) => sum + task.estimatedTime, 0),
actualTimeSpent: 0
};
// Save workflow
this.workflows.set(workflow.id, workflow);
this.persistWorkflow(workflow);
return workflow;
} catch (error) {
throw new WorkflowError({
code: 'WORKFLOW_GENERATION_FAILED',
message: `Failed to generate workflow for user ${userId} on ${date}`,
timestamp: new Date(),
recoverable: true,
suggestedAction: 'Retry workflow generation'
});
}
}
/**
* Get workflow for a specific user and date
*/
getWorkflow(userId: string, date: string): DailyWorkflow | null {
const workflowId = `${userId}-${date}`;
return this.workflows.get(workflowId) || null;
}
/**
* Start a workflow
*/
async startWorkflow(workflowId: string): Promise<DailyWorkflow> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
workflow.workflowStatus = 'in_progress';
workflow.startedAt = new Date();
// Mark first task as in progress
if (workflow.tasks.length > 0) {
workflow.tasks[0].status = 'in_progress';
workflow.tasks[0].startedAt = new Date();
}
this.persistWorkflow(workflow);
return workflow;
}
/**
* Complete a specific task
*/
async completeTask(
workflowId: string,
taskId: string,
completionData?: Partial<TaskCompletionData>
): Promise<WorkflowProgress> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const task = workflow.tasks.find(t => t.id === taskId);
if (!task) {
throw new WorkflowError({
code: 'TASK_NOT_FOUND',
message: `Task ${taskId} not found in workflow ${workflowId}`,
timestamp: new Date(),
recoverable: false
});
}
// Verify task completion
await taskCompletionVerifier.verifyTaskCompletion(task, {
userId: workflow.userId,
timestamp: new Date()
});
// Mark task as completed
task.status = 'completed';
task.completedAt = new Date();
// Calculate time spent
if (task.startedAt) {
const timeSpent = Math.round((task.completedAt.getTime() - task.startedAt.getTime()) / (1000 * 60));
workflow.actualTimeSpent += timeSpent;
}
// Update dependency manager
taskDependencyManager.updateTaskStatus(taskId, 'completed');
// Update workflow progress
workflow.completedTasks++;
// Check if workflow is complete
if (workflow.completedTasks === workflow.totalTasks) {
workflow.workflowStatus = 'completed';
workflow.completedAt = new Date();
}
// Auto-navigate to next task if enabled
if (this.config.autoNavigate) {
const nextTask = taskDependencyManager.getReadyTasks(workflow)[0];
if (nextTask) {
setTimeout(async () => {
try {
await taskNavigationService.navigateToTask(nextTask, workflow);
} catch (error) {
console.warn('Auto-navigation failed:', error);
}
}, 2000); // 2 second delay
}
}
this.persistWorkflow(workflow);
return this.getWorkflowProgress(workflowId);
}
/**
* Skip a task
*/
async skipTask(workflowId: string, taskId: string): Promise<WorkflowProgress> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const task = workflow.tasks.find(t => t.id === taskId);
if (!task) {
throw new WorkflowError({
code: 'TASK_NOT_FOUND',
message: `Task ${taskId} not found in workflow ${workflowId}`,
timestamp: new Date(),
recoverable: false
});
}
task.status = 'skipped';
workflow.completedTasks++;
this.persistWorkflow(workflow);
return this.getWorkflowProgress(workflowId);
}
/**
* Get current workflow progress
*/
getWorkflowProgress(workflowId: string): WorkflowProgress {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const currentTask = workflow.tasks[workflow.currentTaskIndex];
const nextTask = workflow.tasks[workflow.currentTaskIndex + 1];
const remainingTasks = workflow.tasks.slice(workflow.currentTaskIndex + 1);
const estimatedTimeRemaining = remainingTasks.reduce((sum, task) => sum + task.estimatedTime, 0);
return {
completedTasks: workflow.completedTasks,
totalTasks: workflow.totalTasks,
completionPercentage: Math.round((workflow.completedTasks / workflow.totalTasks) * 100),
currentTask,
nextTask,
estimatedTimeRemaining,
actualTimeSpent: workflow.actualTimeSpent
};
}
/**
* Get navigation state for current workflow
*/
getNavigationState(workflowId: string): NavigationState {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const currentTask = workflow.tasks[workflow.currentTaskIndex];
const previousTask = workflow.currentTaskIndex > 0 ? workflow.tasks[workflow.currentTaskIndex - 1] : null;
const nextTask = workflow.currentTaskIndex < workflow.tasks.length - 1 ? workflow.tasks[workflow.currentTaskIndex + 1] : null;
return {
currentTask,
previousTask,
nextTask,
canGoBack: workflow.currentTaskIndex > 0,
canGoForward: workflow.currentTaskIndex < workflow.tasks.length - 1
};
}
/**
* Move to next task in workflow
*/
async moveToNextTask(workflowId: string): Promise<TodayTask | null> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
if (workflow.currentTaskIndex < workflow.tasks.length - 1) {
workflow.currentTaskIndex++;
const nextTask = workflow.tasks[workflow.currentTaskIndex];
// Mark next task as in progress
if (nextTask.status === 'pending') {
nextTask.status = 'in_progress';
nextTask.startedAt = new Date();
}
this.persistWorkflow(workflow);
return nextTask;
}
return null;
}
/**
* Generate tasks for a specific date (enhanced with dependency management)
*/
private async generateTasksForDate(
userId: string,
date: string,
context?: TaskGenerationContext
): Promise<TodayTask[]> {
// This is a placeholder implementation
// In Phase 3, this will be replaced with AI-powered task generation
const defaultTasks: TodayTask[] = [
{
id: `${userId}-${date}-plan-1`,
pillarId: 'plan',
title: 'Review content strategy',
description: 'Check and update your content strategy for the week',
status: 'pending',
priority: 'high',
estimatedTime: 15,
actionType: 'navigate',
actionUrl: '/content-planning-dashboard',
enabled: true,
icon: 'Business',
color: '#4CAF50'
},
{
id: `${userId}-${date}-plan-2`,
pillarId: 'plan',
title: 'Update content calendar',
description: 'Review and update your content calendar',
status: 'pending',
priority: 'medium',
estimatedTime: 10,
dependencies: [`${userId}-${date}-plan-1`],
actionType: 'navigate',
actionUrl: '/content-planning-dashboard',
enabled: true,
icon: 'CalendarMonth',
color: '#4CAF50'
},
{
id: `${userId}-${date}-generate-1`,
pillarId: 'generate',
title: 'Create social media content',
description: 'Generate content for your social media platforms',
status: 'pending',
priority: 'high',
estimatedTime: 30,
dependencies: [`${userId}-${date}-plan-1`, `${userId}-${date}-plan-2`],
actionType: 'navigate',
actionUrl: '/facebook-writer',
enabled: true,
icon: 'AutoAwesome',
color: '#2196F3'
},
{
id: `${userId}-${date}-generate-2`,
pillarId: 'generate',
title: 'Create blog content',
description: 'Write blog posts for your website',
status: 'pending',
priority: 'medium',
estimatedTime: 45,
dependencies: [`${userId}-${date}-plan-1`],
actionType: 'navigate',
actionUrl: '/blog-writer',
enabled: true,
icon: 'Article',
color: '#2196F3'
},
{
id: `${userId}-${date}-publish-1`,
pillarId: 'publish',
title: 'Publish social media content',
description: 'Publish your created content to social media',
status: 'pending',
priority: 'medium',
estimatedTime: 10,
dependencies: [`${userId}-${date}-generate-1`],
actionType: 'navigate',
actionUrl: '/facebook-writer',
enabled: true,
icon: 'Publish',
color: '#FF9800'
},
{
id: `${userId}-${date}-publish-2`,
pillarId: 'publish',
title: 'Publish blog content',
description: 'Publish blog posts to your website',
status: 'pending',
priority: 'medium',
estimatedTime: 15,
dependencies: [`${userId}-${date}-generate-2`],
actionType: 'navigate',
actionUrl: '/blog-writer',
enabled: true,
icon: 'Publish',
color: '#FF9800'
},
{
id: `${userId}-${date}-analyze-1`,
pillarId: 'analyze',
title: 'Review content performance',
description: 'Analyze performance of published content',
status: 'pending',
priority: 'low',
estimatedTime: 20,
dependencies: [`${userId}-${date}-publish-1`, `${userId}-${date}-publish-2`],
actionType: 'navigate',
actionUrl: '/analytics-dashboard',
enabled: true,
icon: 'Analytics',
color: '#9C27B0'
},
{
id: `${userId}-${date}-engage-1`,
pillarId: 'engage',
title: 'Respond to comments',
description: 'Engage with comments on your content',
status: 'pending',
priority: 'low',
estimatedTime: 15,
dependencies: [`${userId}-${date}-publish-1`],
actionType: 'navigate',
actionUrl: '/engagement-dashboard',
enabled: true,
icon: 'ChatBubbleOutline',
color: '#E91E63'
},
// Engage pillar tasks
{
id: `${userId}-${date}-engage-1`,
pillarId: 'engage',
title: 'Reply to blog comment',
description: 'Respond to comments on your latest blog post',
status: 'pending',
priority: 'high',
estimatedTime: 10,
dependencies: [`${userId}-${date}-analyze-1`],
actionType: 'navigate',
actionUrl: '/engagement-dashboard',
enabled: true,
icon: 'Comment',
color: '#E91E63'
},
{
id: `${userId}-${date}-engage-2`,
pillarId: 'engage',
title: 'Respond to Twitter mention',
description: 'Reply to Twitter mentions and engage with followers',
status: 'pending',
priority: 'medium',
estimatedTime: 5,
dependencies: [`${userId}-${date}-engage-1`],
actionType: 'navigate',
actionUrl: '/engagement-dashboard',
enabled: true,
icon: 'Twitter',
color: '#1DA1F2'
},
// Remarket pillar tasks
{
id: `${userId}-${date}-remarket-1`,
pillarId: 'remarket',
title: 'Launch Retargeting Campaign',
description: 'Create and launch targeted remarketing campaigns',
status: 'pending',
priority: 'high',
estimatedTime: 35,
dependencies: [`${userId}-${date}-engage-2`],
actionType: 'navigate',
actionUrl: '/remarketing-dashboard',
enabled: true,
icon: 'Psychology',
color: '#00695C'
},
{
id: `${userId}-${date}-remarket-2`,
pillarId: 'remarket',
title: 'Lead Nurturing Sequence',
description: 'Set up automated lead nurturing workflows',
status: 'pending',
priority: 'medium',
estimatedTime: 30,
dependencies: [`${userId}-${date}-remarket-1`],
actionType: 'navigate',
actionUrl: '/lead-nurturing',
enabled: true,
icon: 'Refresh',
color: '#4CAF50'
}
];
// Validate dependencies and get optimal execution order
const tempWorkflow: DailyWorkflow = {
id: `${userId}-${date}`,
date,
userId,
tasks: defaultTasks,
currentTaskIndex: 0,
completedTasks: 0,
totalTasks: defaultTasks.length,
workflowStatus: 'not_started',
totalEstimatedTime: defaultTasks.reduce((sum, task) => sum + task.estimatedTime, 0),
actualTimeSpent: 0
};
// Validate dependency graph
const validation = taskDependencyManager.validateDependencyGraph(tempWorkflow);
if (!validation.isValid) {
console.warn('Dependency validation failed:', validation.errors);
// Return tasks without dependencies if validation fails
return defaultTasks.map(task => ({ ...task, dependencies: [] }));
}
// Get optimal execution order
const orderedTasks = taskDependencyManager.getOptimalExecutionOrder(tempWorkflow);
return orderedTasks;
}
/**
* Persist workflow to localStorage
*/
private persistWorkflow(workflow: DailyWorkflow): void {
if (this.config.persistProgress) {
try {
localStorage.setItem(`workflow-${workflow.id}`, JSON.stringify(workflow));
} catch (error) {
console.warn('Failed to persist workflow:', error);
}
}
}
/**
* Load persisted workflows from localStorage
*/
private loadPersistedWorkflows(): void {
if (this.config.persistProgress) {
try {
const keys = Object.keys(localStorage).filter(key => key.startsWith('workflow-'));
keys.forEach(key => {
const workflowData = localStorage.getItem(key);
if (workflowData) {
try {
const workflow = JSON.parse(workflowData) as DailyWorkflow;
// Ensure workflow has required properties
if (!workflow.id || !workflow.date || !workflow.userId) {
console.warn(`Invalid workflow data for key ${key}, skipping`);
return;
}
// Ensure tasks array exists and is valid
if (!workflow.tasks || !Array.isArray(workflow.tasks)) {
console.warn(`Invalid tasks array for workflow ${workflow.id}, initializing empty array`);
workflow.tasks = [];
}
// Convert date strings back to Date objects
if (workflow.startedAt) workflow.startedAt = new Date(workflow.startedAt);
if (workflow.completedAt) workflow.completedAt = new Date(workflow.completedAt);
// Process tasks with null checks
workflow.tasks.forEach(task => {
if (task && typeof task === 'object') {
if (task.startedAt) task.startedAt = new Date(task.startedAt);
if (task.completedAt) task.completedAt = new Date(task.completedAt);
}
});
this.workflows.set(workflow.id, workflow);
} catch (parseError) {
console.warn(`Failed to parse workflow data for key ${key}:`, parseError);
// Remove corrupted data
localStorage.removeItem(key);
}
}
});
} catch (error) {
console.warn('Failed to load persisted workflows:', error);
}
}
}
/**
* Clear completed workflows (cleanup)
*/
clearCompletedWorkflows(): void {
const completedWorkflows = Array.from(this.workflows.values())
.filter(workflow => workflow.workflowStatus === 'completed');
completedWorkflows.forEach(workflow => {
this.workflows.delete(workflow.id);
if (this.config.persistProgress) {
localStorage.removeItem(`workflow-${workflow.id}`);
}
});
}
}
// Export singleton instance
export const taskWorkflowOrchestrator = new TaskWorkflowOrchestrator();
export default TaskWorkflowOrchestrator;