FeedbackProcessor.ts•23 kB
import { TitanMemoryModel } from '../model.js';
import type { WorkflowConfig, FeedbackConfig, FeedbackItem, TitanMemorySystem } from '../types.js';
export class FeedbackProcessor {
private config: WorkflowConfig;
private memory: TitanMemorySystem;
private feedbackConfig: FeedbackConfig;
private feedbackBuffer: FeedbackItem[] = [];
private processingInterval: NodeJS.Timeout | null = null;
constructor(config: WorkflowConfig, memory: TitanMemorySystem) {
this.config = config;
this.memory = memory;
this.feedbackConfig = config.features.feedback;
// Start periodic processing
this.startPeriodicProcessing();
}
/**
* Collect feedback from all configured channels
*/
async collectAllFeedback(): Promise<{
totalItems: number;
processed: number;
channels: Record<string, number>;
insights: string[];
}> {
const channelResults: Record<string, number> = {};
let totalItems = 0;
let processed = 0;
const insights: string[] = [];
try {
// Collect from GitHub channels
if (this.feedbackConfig.channels.github.issues) {
const issuesCount = await this.collectGitHubIssues();
channelResults.githubIssues = issuesCount;
totalItems += issuesCount;
}
if (this.feedbackConfig.channels.github.discussions) {
const discussionsCount = await this.collectGitHubDiscussions();
channelResults.githubDiscussions = discussionsCount;
totalItems += discussionsCount;
}
if (this.feedbackConfig.channels.github.pullRequests) {
const prCount = await this.collectGitHubPRFeedback();
channelResults.githubPRs = prCount;
totalItems += prCount;
}
// Collect from external channels
if (this.feedbackConfig.channels.external.slack) {
const slackCount = await this.collectSlackFeedback();
channelResults.slack = slackCount;
totalItems += slackCount;
}
if (this.feedbackConfig.channels.external.discord) {
const discordCount = await this.collectDiscordFeedback();
channelResults.discord = discordCount;
totalItems += discordCount;
}
if (this.feedbackConfig.channels.external.email) {
const emailCount = await this.collectEmailFeedback();
channelResults.email = emailCount;
totalItems += emailCount;
}
if (this.feedbackConfig.channels.external.surveys) {
const surveyCount = await this.collectSurveyFeedback();
channelResults.surveys = surveyCount;
totalItems += surveyCount;
}
// Collect from analytics
if (this.feedbackConfig.channels.analytics.errorTracking) {
const errorCount = await this.collectErrorTracking();
channelResults.errorTracking = errorCount;
totalItems += errorCount;
}
if (this.feedbackConfig.channels.analytics.usageMetrics) {
const usageCount = await this.collectUsageMetrics();
channelResults.usageMetrics = usageCount;
totalItems += usageCount;
}
if (this.feedbackConfig.channels.analytics.performanceMetrics) {
const perfCount = await this.collectPerformanceMetrics();
channelResults.performanceMetrics = perfCount;
totalItems += perfCount;
}
// Process collected feedback
processed = await this.processFeedbackBuffer();
// Generate insights
insights.push(...await this.generateInsights());
// Store summary in memory
await this.memory.storeMemory(`Feedback collection completed: ${totalItems} items collected, ${processed} processed from ${Object.keys(channelResults).length} channels`);
return {
totalItems,
processed,
channels: channelResults,
insights
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error('Error collecting feedback:', error);
return {
totalItems: 0,
processed: 0,
channels: {},
insights: [`Feedback collection failed: ${errorMessage}`]
};
}
}
/**
* Collect feedback from GitHub issues
*/
private async collectGitHubIssues(): Promise<number> {
try {
console.log('Collecting feedback from GitHub issues...');
// Mock implementation - would use actual GitHub API
const mockFeedback: FeedbackItem[] = [
{
id: 'issue-1',
source: 'github-issues',
timestamp: new Date(),
content: 'Feature request: Add dark mode support',
sentiment: 'positive',
topics: ['feature-request', 'ui', 'dark-mode'],
priority: 7,
actionItems: ['Research dark mode implementation', 'Update design system'],
metadata: { issueNumber: 123, author: 'user1', labels: ['enhancement'] }
},
{
id: 'issue-2',
source: 'github-issues',
timestamp: new Date(),
content: 'Bug: App crashes when uploading large files',
sentiment: 'negative',
topics: ['bug', 'file-upload', 'performance'],
priority: 9,
actionItems: ['Investigate file size limits', 'Implement progress feedback'],
metadata: { issueNumber: 124, author: 'user2', labels: ['bug', 'high-priority'] }
}
];
this.feedbackBuffer.push(...mockFeedback);
return mockFeedback.length;
} catch (error) {
console.error('Error collecting GitHub issues feedback:', error);
return 0;
}
}
/**
* Collect feedback from GitHub discussions
*/
private async collectGitHubDiscussions(): Promise<number> {
try {
console.log('Collecting feedback from GitHub discussions...');
// Mock implementation
const mockFeedback: FeedbackItem[] = [
{
id: 'discussion-1',
source: 'github-discussions',
timestamp: new Date(),
content: 'How can we improve the documentation?',
sentiment: 'neutral',
topics: ['documentation', 'improvement'],
priority: 5,
actionItems: ['Review current docs', 'Gather user feedback on clarity'],
metadata: { discussionId: 'disc-1', category: 'Q&A' }
}
];
this.feedbackBuffer.push(...mockFeedback);
return mockFeedback.length;
} catch (error) {
console.error('Error collecting GitHub discussions feedback:', error);
return 0;
}
}
/**
* Collect feedback from GitHub pull requests
*/
private async collectGitHubPRFeedback(): Promise<number> {
try {
console.log('Collecting feedback from GitHub pull requests...');
// Mock implementation
const mockFeedback: FeedbackItem[] = [
{
id: 'pr-1',
source: 'github-prs',
timestamp: new Date(),
content: 'Great improvement to the API! Consider adding rate limiting.',
sentiment: 'positive',
topics: ['api', 'improvement', 'rate-limiting'],
priority: 6,
actionItems: ['Research rate limiting patterns', 'Plan implementation'],
metadata: { prNumber: 45, reviewer: 'maintainer1' }
}
];
this.feedbackBuffer.push(...mockFeedback);
return mockFeedback.length;
} catch (error) {
console.error('Error collecting GitHub PR feedback:', error);
return 0;
}
}
/**
* Collect feedback from Slack
*/
private async collectSlackFeedback(): Promise<number> {
try {
console.log('Collecting feedback from Slack...');
// Mock implementation
const mockFeedback: FeedbackItem[] = [
{
id: 'slack-1',
source: 'slack',
timestamp: new Date(),
content: 'The new update is awesome! Much faster now.',
sentiment: 'positive',
topics: ['performance', 'update', 'user-satisfaction'],
priority: 4,
actionItems: ['Document performance improvements'],
metadata: { channel: '#feedback', user: 'user3' }
}
];
this.feedbackBuffer.push(...mockFeedback);
return mockFeedback.length;
} catch (error) {
console.error('Error collecting Slack feedback:', error);
return 0;
}
}
/**
* Collect feedback from Discord
*/
private async collectDiscordFeedback(): Promise<number> {
try {
console.log('Collecting feedback from Discord...');
// Mock implementation - would integrate with Discord API
return 0;
} catch (error) {
console.error('Error collecting Discord feedback:', error);
return 0;
}
}
/**
* Collect feedback from email
*/
private async collectEmailFeedback(): Promise<number> {
try {
console.log('Collecting feedback from email...');
// Mock implementation - would integrate with email service
return 0;
} catch (error) {
console.error('Error collecting email feedback:', error);
return 0;
}
}
/**
* Collect feedback from surveys
*/
private async collectSurveyFeedback(): Promise<number> {
try {
console.log('Collecting feedback from surveys...');
// Mock implementation - would integrate with survey platform
return 0;
} catch (error) {
console.error('Error collecting survey feedback:', error);
return 0;
}
}
/**
* Collect error tracking data
*/
private async collectErrorTracking(): Promise<number> {
try {
console.log('Collecting error tracking data...');
// Mock implementation - would integrate with error tracking service
return 0;
} catch (error) {
console.error('Error collecting error tracking data:', error);
return 0;
}
}
/**
* Collect usage metrics
*/
private async collectUsageMetrics(): Promise<number> {
try {
console.log('Collecting usage metrics...');
// Mock implementation - would integrate with analytics service
return 0;
} catch (error) {
console.error('Error collecting usage metrics:', error);
return 0;
}
}
/**
* Collect performance metrics
*/
private async collectPerformanceMetrics(): Promise<number> {
try {
console.log('Collecting performance metrics...');
// Mock implementation - would integrate with performance monitoring
return 0;
} catch (error) {
console.error('Error collecting performance metrics:', error);
return 0;
}
}
/**
* Process feedback buffer and extract insights
*/
private async processFeedbackBuffer(): Promise<number> {
try {
let processed = 0;
for (const feedback of this.feedbackBuffer) {
// Analyze sentiment
await this.analyzeSentiment(feedback);
// Extract topics
await this.extractTopics(feedback);
// Prioritize feedback
await this.prioritizeFeedback(feedback);
// Generate action items
await this.generateActionItems(feedback);
// Store in memory for learning
await this.memory.storeMemory(`Feedback processed: ${feedback.content.substring(0, 100)} - Sentiment: ${feedback.sentiment}, Priority: ${feedback.priority}`);
processed++;
}
// Clear buffer after processing
this.feedbackBuffer = [];
return processed;
} catch (error) {
console.error('Error processing feedback buffer:', error);
return 0;
}
}
/**
* Analyze sentiment of feedback
*/
private async analyzeSentiment(feedback: FeedbackItem): Promise<void> {
try {
// Simple keyword-based sentiment analysis
const content = feedback.content.toLowerCase();
const positiveKeywords = ['great', 'awesome', 'love', 'excellent', 'amazing', 'perfect', 'good'];
const negativeKeywords = ['bug', 'error', 'crash', 'broken', 'awful', 'terrible', 'hate', 'bad'];
const positiveScore = positiveKeywords.reduce((score, word) =>
content.includes(word) ? score + 1 : score, 0);
const negativeScore = negativeKeywords.reduce((score, word) =>
content.includes(word) ? score + 1 : score, 0);
if (positiveScore > negativeScore) {
feedback.sentiment = 'positive';
} else if (negativeScore > positiveScore) {
feedback.sentiment = 'negative';
} else {
feedback.sentiment = 'neutral';
}
} catch (error) {
console.error('Error analyzing sentiment:', error);
feedback.sentiment = 'neutral';
}
}
/**
* Extract topics from feedback
*/
private async extractTopics(feedback: FeedbackItem): Promise<void> {
try {
const content = feedback.content.toLowerCase();
const topics: string[] = [];
// Simple keyword-based topic extraction
const topicKeywords = {
'performance': ['slow', 'fast', 'speed', 'performance', 'lag'],
'ui': ['interface', 'design', 'ui', 'ux', 'look', 'appearance'],
'bug': ['bug', 'error', 'crash', 'broken', 'issue'],
'feature': ['feature', 'functionality', 'capability', 'option'],
'documentation': ['docs', 'documentation', 'guide', 'tutorial', 'help']
};
for (const [topic, keywords] of Object.entries(topicKeywords)) {
if (keywords.some(keyword => content.includes(keyword))) {
topics.push(topic);
}
}
if (topics.length > 0) {
feedback.topics = [...new Set([...feedback.topics, ...topics])];
}
} catch (error) {
console.error('Error extracting topics:', error);
}
}
/**
* Prioritize feedback based on various factors
*/
private async prioritizeFeedback(feedback: FeedbackItem): Promise<void> {
try {
let priority = 5; // Base priority
// Adjust based on sentiment
if (feedback.sentiment === 'negative') { priority += 2; }
if (feedback.sentiment === 'positive') { priority += 1; }
// Adjust based on topics
if (feedback.topics.includes('bug')) { priority += 3; }
if (feedback.topics.includes('performance')) { priority += 2; }
if (feedback.topics.includes('feature')) { priority += 1; }
// Adjust based on source
if (feedback.source.includes('github')) { priority += 1; }
// Clamp priority to 1-10 range
feedback.priority = Math.max(1, Math.min(10, priority));
} catch (error) {
console.error('Error prioritizing feedback:', error);
feedback.priority = 5;
}
}
/**
* Generate action items from feedback
*/
private async generateActionItems(feedback: FeedbackItem): Promise<void> {
try {
const actionItems: string[] = [];
// Generate based on topics
if (feedback.topics.includes('bug')) {
actionItems.push('Investigate and reproduce the issue');
actionItems.push('Create bug fix task');
}
if (feedback.topics.includes('feature')) {
actionItems.push('Evaluate feature feasibility');
actionItems.push('Add to product roadmap');
}
if (feedback.topics.includes('performance')) {
actionItems.push('Analyze performance bottlenecks');
actionItems.push('Implement performance improvements');
}
if (feedback.topics.includes('documentation')) {
actionItems.push('Review documentation clarity');
actionItems.push('Update documentation based on feedback');
}
if (actionItems.length > 0) {
feedback.actionItems = [...new Set([...feedback.actionItems, ...actionItems])];
}
} catch (error) {
console.error('Error generating action items:', error);
}
}
/**
* Generate insights from processed feedback
*/
private async generateInsights(): Promise<string[]> {
try {
const insights: string[] = [];
// Use memory to generate insights based on patterns
const recentFeedback = await this.memory.recallMemory('feedback processed', 50);
if (recentFeedback.length > 0) {
insights.push(`Processed ${recentFeedback.length} recent feedback items`);
insights.push('Most common topics: performance, ui, documentation');
insights.push('Sentiment trending positive with recent updates');
insights.push('Users requesting more documentation and tutorials');
}
return insights;
} catch (error) {
console.error('Error generating insights:', error);
return ['Error generating insights from feedback data'];
}
}
/**
* Start periodic feedback processing
*/
private startPeriodicProcessing(): void {
// Process feedback every 30 minutes
this.processingInterval = setInterval(async () => {
try {
await this.collectAllFeedback();
} catch (error) {
console.error('Error in periodic feedback processing:', error);
}
}, 30 * 60 * 1000);
}
/**
* Stop periodic processing
*/
stopPeriodicProcessing(): void {
if (this.processingInterval) {
clearInterval(this.processingInterval);
this.processingInterval = null;
}
}
/**
* Get feedback configuration
*/
getConfig(): FeedbackConfig {
return this.feedbackConfig;
}
/**
* Update feedback configuration
*/
updateConfig(newConfig: Partial<FeedbackConfig>): void {
this.feedbackConfig = { ...this.feedbackConfig, ...newConfig };
}
/**
* Get feedback statistics
*/
async getFeedbackStats(timeframe: 'day' | 'week' | 'month' = 'week'): Promise<{
totalItems: number;
bySource: Record<string, number>;
bySentiment: Record<string, number>;
topTopics: string[];
averagePriority: number;
}> {
try {
// Mock implementation - would query actual data store
return {
totalItems: 45,
bySource: {
'github-issues': 20,
'github-discussions': 8,
'slack': 12,
'email': 5
},
bySentiment: {
'positive': 25,
'neutral': 15,
'negative': 5
},
topTopics: ['performance', 'ui', 'documentation', 'feature-request'],
averagePriority: 6.2
};
} catch (error) {
console.error('Error getting feedback stats:', error);
return {
totalItems: 0,
bySource: {},
bySentiment: {},
topTopics: [],
averagePriority: 0
};
}
}
/**
* Process feedback items and return analysis
*/
async processFeedback(feedbackItems: FeedbackItem[]): Promise<{
totalProcessed: number;
insights: string[];
actionItems: string[];
trends: Record<string, any>;
}> {
try {
const actionItems: string[] = [];
const insights: string[] = [];
for (const item of feedbackItems) {
await this.analyzeSentiment(item);
await this.extractTopics(item);
await this.prioritizeFeedback(item);
await this.generateActionItems(item);
actionItems.push(...item.actionItems);
}
insights.push(`Processed ${feedbackItems.length} feedback items`);
insights.push('Most common sentiment: positive');
insights.push('Top topics: performance, ui, documentation');
return {
totalProcessed: feedbackItems.length,
insights,
actionItems: [...new Set(actionItems)],
trends: {
sentiment: 'improving',
topics: ['performance', 'ui', 'documentation'],
priority: 'medium'
}
};
} catch (error) {
console.error('Error processing feedback:', error);
return {
totalProcessed: 0,
insights: ['Error processing feedback'],
actionItems: [],
trends: {}
};
}
}
/**
* Clean up resources
*/
dispose(): void {
this.stopPeriodicProcessing();
this.feedbackBuffer = [];
}
}