UNPKG

quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

249 lines 8.58 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { promises as fs } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; export class FeedbackService { feedbackDir; feedbackFile; statsFile; usageFile; constructor() { this.feedbackDir = join(homedir(), '.quantum'); this.feedbackFile = join(this.feedbackDir, 'feedback.json'); this.statsFile = join(this.feedbackDir, 'feedback-stats.json'); this.usageFile = join(this.feedbackDir, 'usage-stats.json'); } async initialize() { try { await fs.mkdir(this.feedbackDir, { recursive: true }); // Initialize files if they don't exist await this.ensureFileExists(this.feedbackFile, '[]'); await this.ensureFileExists(this.statsFile, JSON.stringify(this.getDefaultStats())); await this.ensureFileExists(this.usageFile, JSON.stringify(this.getDefaultUsageStats())); } catch (error) { console.warn('Failed to initialize feedback service:', error); } } async ensureFileExists(filePath, defaultContent) { try { await fs.access(filePath); } catch { await fs.writeFile(filePath, defaultContent, 'utf8'); } } async saveFeedback(feedback) { try { const existingFeedback = await this.loadFeedback(); existingFeedback.push(feedback); // Keep only last 1000 feedback entries to prevent unbounded growth const trimmedFeedback = existingFeedback.slice(-1000); await fs.writeFile(this.feedbackFile, JSON.stringify(trimmedFeedback, null, 2), 'utf8'); await this.updateStats(); } catch (error) { console.warn('Failed to save feedback:', error); } } async loadFeedback() { try { const content = await fs.readFile(this.feedbackFile, 'utf8'); const feedback = JSON.parse(content); // Convert timestamp strings back to Date objects return feedback.map((entry) => ({ ...entry, timestamp: new Date(entry.timestamp), })); } catch (error) { console.warn('Failed to load feedback:', error); return []; } } async getFeedbackStats() { try { const content = await fs.readFile(this.statsFile, 'utf8'); const stats = JSON.parse(content); return { ...stats, lastUpdated: new Date(stats.lastUpdated), }; } catch (error) { console.warn('Failed to load feedback stats:', error); return this.getDefaultStats(); } } async recordUsage(action, metadata) { try { const stats = await this.getUsageStats(); // Update counters based on action switch (action) { case 'session_start': stats.totalSessions++; break; case 'query_sent': stats.totalQueries++; break; case 'collaboration_enabled': stats.collaborationEnabled++; break; case 'verify_used': stats.verifyUsage++; break; case 'compare_used': stats.compareUsage++; break; case 'confidence_used': stats.confidenceUsage++; break; } stats.lastActive = new Date(); await fs.writeFile(this.usageFile, JSON.stringify(stats, null, 2), 'utf8'); } catch (error) { console.warn('Failed to record usage:', error); } } async getUsageStats() { try { const content = await fs.readFile(this.usageFile, 'utf8'); const stats = JSON.parse(content); return { ...stats, lastActive: new Date(stats.lastActive), }; } catch (error) { console.warn('Failed to load usage stats:', error); return this.getDefaultUsageStats(); } } async exportFeedback(outputPath) { try { const feedback = await this.loadFeedback(); const stats = await this.getFeedbackStats(); const usage = await this.getUsageStats(); const exportData = { feedback, stats, usage, exportedAt: new Date(), version: '1.0', }; const exportPath = outputPath || join(this.feedbackDir, `feedback-export-${Date.now()}.json`); await fs.writeFile(exportPath, JSON.stringify(exportData, null, 2), 'utf8'); return exportPath; } catch (error) { throw new Error(`Failed to export feedback: ${error}`); } } async clearOldFeedback(daysToKeep = 30) { try { const feedback = await this.loadFeedback(); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysToKeep); const recentFeedback = feedback.filter((entry) => entry.timestamp > cutoffDate); const removedCount = feedback.length - recentFeedback.length; if (removedCount > 0) { await fs.writeFile(this.feedbackFile, JSON.stringify(recentFeedback, null, 2), 'utf8'); await this.updateStats(); } return removedCount; } catch (error) { console.warn('Failed to clear old feedback:', error); return 0; } } async updateStats() { try { const feedback = await this.loadFeedback(); const stats = this.calculateStats(feedback); await fs.writeFile(this.statsFile, JSON.stringify(stats, null, 2), 'utf8'); } catch (error) { console.warn('Failed to update stats:', error); } } calculateStats(feedback) { const stats = { totalFeedback: feedback.length, qualityRatings: { thumbs_up: 0, thumbs_down: 0, neutral: 0, }, verificationUseful: 0, verificationNotUseful: 0, averageRating: 0, lastUpdated: new Date(), }; let totalRatingScore = 0; let ratingCount = 0; for (const entry of feedback) { if (entry.type === 'quality') { stats.qualityRatings[entry.rating]++; // Convert rating to numeric score for average calculation let score = 0; switch (entry.rating) { case 'thumbs_up': score = 1; break; case 'neutral': score = 0.5; break; case 'thumbs_down': score = 0; break; } totalRatingScore += score; ratingCount++; } else if (entry.type === 'verification') { if (entry.rating === 'thumbs_up') { stats.verificationUseful++; } else if (entry.rating === 'thumbs_down') { stats.verificationNotUseful++; } } } stats.averageRating = ratingCount > 0 ? totalRatingScore / ratingCount : 0; return stats; } getDefaultStats() { return { totalFeedback: 0, qualityRatings: { thumbs_up: 0, thumbs_down: 0, neutral: 0, }, verificationUseful: 0, verificationNotUseful: 0, averageRating: 0, lastUpdated: new Date(), }; } getDefaultUsageStats() { return { totalSessions: 0, totalQueries: 0, collaborationEnabled: 0, verifyUsage: 0, compareUsage: 0, confidenceUsage: 0, averageSessionLength: 0, lastActive: new Date(), }; } } //# sourceMappingURL=feedbackService.js.map