UNPKG

c9ai

Version:

Universal AI assistant with vibe-based workflows, hybrid cloud+local AI, and comprehensive tool integration

649 lines (561 loc) 20.2 kB
"use strict"; /** * Vibe-Based Workflow API * Serves template discovery, vibe matching, and workflow customization */ const express = require('express'); const { VibeWorkflowEngine } = require('../src/workflows/template-engine'); const { VibeMatcher } = require('../src/workflows/vibe-matcher'); const { ToolMapper } = require('../src/workflows/tool-mapper'); class WorkflowAPI { constructor() { this.workflowEngine = new VibeWorkflowEngine(); this.vibeMatcher = new VibeMatcher(); this.toolMapper = new ToolMapper(); this.router = express.Router(); this.setupRoutes(); } async initialize() { await this.workflowEngine.initialize(); console.log('✅ Workflow API initialized'); } setupRoutes() { // Vibe Detection & Analysis this.router.post('/api/vibe/detect', this.detectVibe.bind(this)); this.router.post('/api/vibe/recommendations', this.getVibeRecommendations.bind(this)); // Template Discovery this.router.get('/api/workflows/templates', this.getTemplates.bind(this)); this.router.get('/api/workflows/templates/:id', this.getTemplate.bind(this)); this.router.post('/api/workflows/templates/generate', this.generateTemplate.bind(this)); this.router.post('/api/workflows/templates/match', this.matchTemplates.bind(this)); // Template Customization this.router.post('/api/workflows/templates/adapt', this.adaptTemplate.bind(this)); this.router.post('/api/workflows/templates/create', this.createCustomTemplate.bind(this)); // Workflow Execution this.router.post('/api/workflows/start', this.startWorkflow.bind(this)); this.router.post('/api/workflows/execute-step', this.executeWorkflowStep.bind(this)); this.router.post('/api/workflows/complete', this.completeWorkflow.bind(this)); // User Profiles & Learning this.router.get('/api/workflows/profile/:userId', this.getUserProfile.bind(this)); this.router.post('/api/workflows/feedback', this.recordFeedback.bind(this)); // Workflow Analytics this.router.get('/api/workflows/analytics/:userId', this.getUserAnalytics.bind(this)); } /** * POST /api/vibe/detect * Analyze current user context to detect vibe */ async detectVibe(req, res) { try { const contextSignals = req.body; // Validate required fields if (!contextSignals.energyLevel) { return res.status(400).json({ error: 'energyLevel is required' }); } const vibeAnalysis = this.vibeMatcher.detectCurrentVibe(contextSignals); res.json({ success: true, vibe: vibeAnalysis, timestamp: new Date().toISOString(), contextAnalyzed: Object.keys(contextSignals) }); } catch (error) { console.error('Vibe detection error:', error); res.status(500).json({ error: 'Failed to detect vibe', message: error.message }); } } /** * POST /api/vibe/recommendations * Get workflow recommendations based on detected vibe */ async getVibeRecommendations(req, res) { try { const { vibeAnalysis, userPreferences = {} } = req.body; if (!vibeAnalysis) { return res.status(400).json({ error: 'vibeAnalysis is required' }); } const recommendations = this.vibeMatcher.getVibeRecommendations( vibeAnalysis, userPreferences ); res.json({ success: true, recommendations, generatedAt: new Date().toISOString() }); } catch (error) { console.error('Recommendations error:', error); res.status(500).json({ error: 'Failed to generate recommendations', message: error.message }); } } /** * GET /api/workflows/templates * Get all available workflow templates */ async getTemplates(req, res) { try { // Reload templates from disk so newly added files show up without restart await this.workflowEngine.loadTemplates(); const { category, vibe, duration, tags } = req.query; const templates = Array.from(this.workflowEngine.templates.values()); let filteredTemplates = templates; // Apply filters if (category) { filteredTemplates = filteredTemplates.filter(t => t.vibe.tags?.includes(category) ); } if (vibe) { filteredTemplates = filteredTemplates.filter(t => t.vibe.energy === vibe || t.vibe.mood === vibe ); } if (duration) { const maxDuration = parseInt(duration); filteredTemplates = filteredTemplates.filter(t => { const templateDuration = this.workflowEngine.parseDuration(t.vibe.duration); return templateDuration <= maxDuration; }); } if (tags) { const tagList = tags.split(','); filteredTemplates = filteredTemplates.filter(t => tagList.some(tag => t.vibe.tags?.includes(tag)) ); } // Format response const responseTemplates = filteredTemplates.map(template => ({ id: template.id, name: template.name, description: template.description, vibe: template.vibe, estimatedDuration: template.vibe.duration, estimatedCredits: template.credits?.estimated || 0, tags: template.vibe.tags || [], stepCount: template.flow?.length || 0, type: template.type || 'default' })); res.json({ success: true, templates: responseTemplates, total: responseTemplates.length, filters: { category, vibe, duration, tags } }); } catch (error) { console.error('Get templates error:', error); res.status(500).json({ error: 'Failed to retrieve templates', message: error.message }); } } /** * GET /api/workflows/templates/:id * Get specific template with full details */ async getTemplate(req, res) { try { const { id } = req.params; const template = this.workflowEngine.templates.get(id); if (!template) { return res.status(404).json({ error: 'Template not found', templateId: id }); } res.json({ success: true, template: template }); } catch (error) { console.error('Get template error:', error); res.status(500).json({ error: 'Failed to retrieve template', message: error.message }); } } /** * POST /api/workflows/templates/generate * Generate and save a workflow from a cloud provider using a plain prompt * Body: { provider?: 'claude'|'gemini'|'openai', id?: string, name?: string, goal: string } */ async generateTemplate(req, res) { try { const { provider = 'claude', id, name, goal } = req.body || {}; if (!goal || typeof goal !== 'string') { return res.status(400).json({ error: 'goal is required' }); } const { generateAndSaveWorkflow } = require('../src/workflows/generate-from-ai'); const out = await generateAndSaveWorkflow({ provider, id, name, goal }); // reload templates so the new one is immediately available await this.workflowEngine.loadTemplates(); return res.json({ success: true, file: out.file, template: out.template }); } catch (error) { console.error('Generate template error:', error); return res.status(500).json({ error: 'Failed to generate workflow', details: error.message }); } } /** * POST /api/workflows/templates/match * Find templates that match user context */ async matchTemplates(req, res) { try { const userContext = req.body; const matchingTemplates = await this.workflowEngine.matchTemplatesForVibe(userContext); res.json({ success: true, matches: matchingTemplates, matchCount: matchingTemplates.length, userContext: { energyLevel: userContext.energyLevel, availableTime: userContext.availableTime, workContext: userContext.workContext } }); } catch (error) { console.error('Template matching error:', error); res.status(500).json({ error: 'Failed to match templates', message: error.message }); } } /** * POST /api/workflows/templates/adapt * Adapt template to user constraints */ async adaptTemplate(req, res) { try { const { templateId, constraints } = req.body; const template = this.workflowEngine.templates.get(templateId); if (!template) { return res.status(404).json({ error: 'Template not found', templateId }); } const adaptedTemplate = this.workflowEngine.adaptTemplate(template, constraints); res.json({ success: true, originalTemplate: { id: template.id, name: template.name, duration: template.vibe.duration }, adaptedTemplate: adaptedTemplate, adaptationApplied: adaptedTemplate.activeAdaptation || 'none', constraints: constraints }); } catch (error) { console.error('Template adaptation error:', error); res.status(500).json({ error: 'Failed to adapt template', message: error.message }); } } /** * POST /api/workflows/templates/create * Create custom template from user workflow */ async createCustomTemplate(req, res) { try { const { userId, workflowData } = req.body; if (!userId || !workflowData) { return res.status(400).json({ error: 'userId and workflowData are required' }); } const customTemplate = await this.workflowEngine.createCustomTemplate(userId, workflowData); res.json({ success: true, template: customTemplate, message: 'Custom template created successfully' }); } catch (error) { console.error('Custom template creation error:', error); res.status(500).json({ error: 'Failed to create custom template', message: error.message }); } } /** * POST /api/workflows/start * Start workflow session with tracking */ async startWorkflow(req, res) { try { const { userId, templateId, adaptedTemplate, sessionContext } = req.body; const template = adaptedTemplate || this.workflowEngine.templates.get(templateId); if (!template) { return res.status(404).json({ error: 'Template not found' }); } const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const workflowSession = { sessionId, userId, templateId: template.id, templateName: template.name, startTime: new Date().toISOString(), flow: template.flow, currentStep: 0, status: 'active', estimatedDuration: template.vibe.duration, estimatedCredits: template.credits?.estimated || 0, sessionContext: sessionContext || {}, adaptationUsed: template.activeAdaptation || null }; // In production, store session in database // For now, return the session data res.json({ success: true, session: workflowSession, nextStep: template.flow?.[0] || null, message: 'Workflow session started successfully' }); } catch (error) { console.error('Start workflow error:', error); res.status(500).json({ error: 'Failed to start workflow', message: error.message }); } } /** * POST /api/workflows/complete * Complete workflow session and record feedback */ async completeWorkflow(req, res) { try { const { sessionId, userId, completionData, feedback } = req.body; const completionRecord = { sessionId, userId, completedAt: new Date().toISOString(), actualDuration: completionData.actualDuration, stepsCompleted: completionData.stepsCompleted, totalSteps: completionData.totalSteps, creditsUsed: completionData.creditsUsed, satisfaction: feedback.satisfaction, energyAfter: feedback.energyAfter, wouldUseAgain: feedback.wouldUseAgain, improvements: feedback.improvements }; // Record vibe session for learning if (feedback.vibeDetection) { await this.vibeMatcher.recordVibeSession(userId, { detectedVibe: feedback.vibeDetection.primaryVibe?.[0], selectedTemplate: { id: completionData.templateId, vibe: completionData.templateVibe }, sessionSatisfaction: feedback.satisfaction, actualDuration: completionData.actualDuration, toolsUsed: completionData.toolsUsed || [], completionRate: completionData.stepsCompleted / completionData.totalSteps, energyBefore: feedback.energyBefore, energyAfter: feedback.energyAfter }); } res.json({ success: true, completion: completionRecord, message: 'Workflow completed successfully', learningRecorded: !!feedback.vibeDetection }); } catch (error) { console.error('Complete workflow error:', error); res.status(500).json({ error: 'Failed to complete workflow', message: error.message }); } } /** * POST /api/workflows/execute-step * Execute workflow step with proper tool mapping */ async executeWorkflowStep(req, res) { try { const { step, userInputs, stepIndex, provider = 'llamacpp' } = req.body; if (!step) { return res.status(400).json({ error: 'step is required' }); } // Map template tools to actual C9AI tools const mappedToolInfo = this.toolMapper.mapTools(step.tools || []); // Generate tool-specific prompt const toolPrompt = this.toolMapper.generateToolPrompt(step, userInputs || {}, mappedToolInfo); // Set up response for SSE streaming res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control' }); // Import agent runner here to avoid circular dependencies const { runStep } = require('../src/agent/runStep'); // Execute with mapped tools const stepData = { prompt: toolPrompt, provider: provider, allow: mappedToolInfo.tools // Use mapped tools instead of template tools }; // Set up progress callback for SSE const progressCallback = (data) => { try { res.write(`data: ${JSON.stringify(data)}\n\n`); } catch (error) { console.error('SSE write error:', error); } }; // Execute the step try { await runStep(stepData, progressCallback); // Send completion if (!res.headersSent) { res.write(`data: ${JSON.stringify({ type: 'final', message: 'Step completed successfully', mappedTools: mappedToolInfo.tools, originalTools: mappedToolInfo.originalTools })}\n\n`); } } catch (stepError) { console.error('Step execution error:', stepError); if (!res.headersSent) { res.write(`data: ${JSON.stringify({ type: 'error', error: stepError.message, mappedTools: mappedToolInfo.tools, originalTools: mappedToolInfo.originalTools })}\n\n`); } } res.end(); } catch (error) { console.error('Execute workflow step error:', error); if (!res.headersSent) { res.status(500).json({ error: 'Failed to execute workflow step', message: error.message }); } } } /** * GET /api/workflows/profile/:userId * Get user workflow profile and preferences */ async getUserProfile(req, res) { try { const { userId } = req.params; const userProfile = await this.workflowEngine.getUserProfile(userId); res.json({ success: true, profile: userProfile }); } catch (error) { console.error('Get user profile error:', error); res.status(500).json({ error: 'Failed to retrieve user profile', message: error.message }); } } /** * POST /api/workflows/feedback * Record workflow feedback for improvements */ async recordFeedback(req, res) { try { const { userId, feedbackData } = req.body; const feedbackRecord = { userId, timestamp: new Date().toISOString(), ...feedbackData }; // In production, store feedback in database console.log('📝 Workflow feedback recorded:', feedbackRecord); res.json({ success: true, message: 'Feedback recorded successfully' }); } catch (error) { console.error('Record feedback error:', error); res.status(500).json({ error: 'Failed to record feedback', message: error.message }); } } /** * GET /api/workflows/analytics/:userId * Get user workflow analytics and insights */ async getUserAnalytics(req, res) { try { const { userId } = req.params; const { timeframe = '30d' } = req.query; // In production, query analytics from database const mockAnalytics = { userId, timeframe, totalSessions: 45, totalMinutes: 2340, averageSatisfaction: 4.2, mostUsedVibes: [ { vibe: "fresh-focused", count: 18, satisfaction: 4.5 }, { vibe: "creative-burst", count: 12, satisfaction: 4.3 }, { vibe: "productive-sprint", count: 10, satisfaction: 4.0 } ], mostUsedTemplates: [ { template: "morning-content-creator", usage: 15, satisfaction: 4.4 }, { template: "data-detective", usage: 12, satisfaction: 4.6 }, { template: "rapid-prototype", usage: 8, satisfaction: 4.1 } ], vibeAccuracy: 0.78, energyTrends: { beforeSessions: { high: 0.4, medium: 0.45, low: 0.15 }, afterSessions: { high: 0.35, medium: 0.55, low: 0.1 } }, recommendations: [ "Your satisfaction is highest with morning focused sessions", "Consider trying more analytical templates", "Your energy levels tend to stabilize after workflow sessions" ] }; res.json({ success: true, analytics: mockAnalytics, generatedAt: new Date().toISOString() }); } catch (error) { console.error('Get user analytics error:', error); res.status(500).json({ error: 'Failed to retrieve analytics', message: error.message }); } } } // Create and initialize API const workflowAPI = new WorkflowAPI(); module.exports = { workflowRouter: workflowAPI.router, initializeWorkflowAPI: () => workflowAPI.initialize() };