UNPKG

@aituber-onair/manneri

Version:

A lightweight conversation pattern detection library to prevent repetitive AI responses

251 lines 9.48 kB
import { DEFAULT_MANNERI_CONFIG } from '../types/index.js'; import { ConversationAnalyzer, } from './ConversationAnalyzer.js'; import { PromptGenerator, } from '../generators/PromptGenerator.js'; import { PatternDetector } from '../analyzers/PatternDetector.js'; import { createEventEmitter } from '../utils/browserUtils.js'; export class ManneriDetector { constructor(config = {}, options = {}) { this.eventEmitter = createEventEmitter(); this.interventionHistory = []; this.lastAnalysisResult = null; this.config = { ...DEFAULT_MANNERI_CONFIG, ...config }; this.persistenceProvider = options.persistenceProvider; const language = this.config.language || 'ja'; const customPrompts = this.config.customPrompts; this.analyzer = new ConversationAnalyzer(this.createAnalyzerOptions()); this.promptGenerator = new PromptGenerator(language, customPrompts); this.patternDetector = new PatternDetector(); } detectManneri(messages) { if (messages.length < 2) return false; const result = this.analyzer.analyzeConversation(messages); this.lastAnalysisResult = result; this.emit('similarity_calculated', { score: result.similarity.score, threshold: this.config.similarityThreshold, }); if (result.shouldIntervene) { this.emit('pattern_detected', result); } return result.shouldIntervene; } shouldIntervene(messages) { if (!this.detectManneri(messages)) return false; const now = Date.now(); const lastIntervention = this.interventionHistory[this.interventionHistory.length - 1] || 0; const timeSinceLastIntervention = now - lastIntervention; const shouldInterveneByTime = timeSinceLastIntervention >= this.config.interventionCooldown; if (!shouldInterveneByTime && this.config.debugMode) { console.log(`[Manneri] Intervention skipped due to cooldown. Time remaining: ${this.config.interventionCooldown - timeSinceLastIntervention}ms`); } return shouldInterveneByTime; } generateDiversificationPrompt(messages) { const options = { language: this.config.language, }; const prompt = this.promptGenerator.generateDiversificationPrompt(messages, options); this.recordIntervention(); this.emit('intervention_triggered', prompt); return prompt; } async generateAiDiversificationPrompt(messages) { return this.generateDiversificationPrompt(messages); } analyzeConversation(messages) { const result = this.analyzer.analyzeConversation(messages); this.lastAnalysisResult = result; return result; } updateConfig(newConfig) { Object.assign(this.config, newConfig); // Update language and prompts if changed if (newConfig.language || newConfig.customPrompts) { const language = this.config.language || 'ja'; const customPrompts = this.config.customPrompts; // Update prompt generator and pattern detector this.promptGenerator = new PromptGenerator(language, customPrompts); this.patternDetector = new PatternDetector(); } this.analyzer.updateOptions(this.createAnalyzerOptions()); this.emit('config_updated', newConfig); } getConfig() { return { ...this.config }; } on(event, handler) { this.eventEmitter.on(event, handler); } off(event, handler) { this.eventEmitter.off(event, handler); } getStatistics() { const totalInterventions = this.interventionHistory.length; let averageInterval = 0; if (totalInterventions > 1) { const intervals = []; for (let i = 1; i < this.interventionHistory.length; i++) { intervals.push(this.interventionHistory[i] - this.interventionHistory[i - 1]); } averageInterval = intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length; } return { totalInterventions, averageInterventionInterval: Math.round(averageInterval), lastIntervention: this.interventionHistory[this.interventionHistory.length - 1] || null, configuredThresholds: { similarity: this.config.similarityThreshold, repetition: this.config.repetitionLimit, cooldown: this.config.interventionCooldown, }, analysisStats: this.analyzer.getAnalysisStats(), }; } clearHistory() { this.interventionHistory = []; this.lastAnalysisResult = null; this.analyzer.clearCache(); this.promptGenerator.clearHistory(); } exportData() { return { patterns: this.lastAnalysisResult?.patterns || [], interventions: this.interventionHistory, settings: this.config, lastCleanup: Date.now(), }; } importData(data) { if (data.interventions) { this.interventionHistory = data.interventions; } if (data.settings) { this.updateConfig(data.settings); } } createAnalyzerOptions() { return { similarityThreshold: this.config.similarityThreshold, patternThreshold: 0.8, keywordThreshold: 0.7, analysisWindow: this.config.lookbackWindow, enableSimilarityAnalysis: true, enablePatternDetection: true, enableKeywordAnalysis: this.config.enableKeywordAnalysis, enableTopicTracking: this.config.enableTopicTracking, textAnalysisOptions: { minWordLength: 2, includeStopWords: false, language: 'auto', }, }; } recordIntervention() { const now = Date.now(); this.interventionHistory.push(now); const maxHistory = 100; if (this.interventionHistory.length > maxHistory) { this.interventionHistory = this.interventionHistory.slice(-maxHistory); } } emit(event, data) { if (this.config.debugMode) { console.log(`[Manneri] Event: ${event}`, data); } this.eventEmitter.emit(event, data); } /** * Manually save data using the persistence provider */ async save() { if (!this.persistenceProvider) { console.warn('ManneriDetector: No persistence provider configured'); return false; } try { const data = this.exportData(); const result = await this.persistenceProvider.save(data); if (result) { this.emit('save_success', { timestamp: Date.now() }); } return result; } catch (error) { this.emit('save_error', { error: error }); return false; } } /** * Manually load data using the persistence provider */ async load() { if (!this.persistenceProvider) { console.warn('ManneriDetector: No persistence provider configured'); return false; } try { const data = await this.persistenceProvider.load(); if (data) { this.importData(data); this.emit('load_success', { data, timestamp: Date.now() }); return true; } return false; } catch (error) { this.emit('load_error', { error: error }); return false; } } /** * Manually cleanup old data */ async cleanup(maxAge = 7 * 24 * 60 * 60 * 1000) { const cutoff = Date.now() - maxAge; const originalLength = this.interventionHistory.length; this.interventionHistory = this.interventionHistory.filter((timestamp) => timestamp > cutoff); const removed = originalLength - this.interventionHistory.length; try { let providerRemoved = 0; if (this.persistenceProvider?.cleanup) { providerRemoved = await this.persistenceProvider.cleanup(maxAge); } const totalRemoved = removed + providerRemoved; if (totalRemoved > 0) { this.emit('cleanup_completed', { removedItems: totalRemoved, timestamp: Date.now(), }); } return totalRemoved; } catch (error) { this.emit('cleanup_error', { error: error }); return removed; // Return at least the memory cleanup count } } /** * Check if persistence provider is configured */ hasPersistenceProvider() { return this.persistenceProvider !== undefined; } /** * Get persistence provider info (if available) */ getPersistenceInfo() { if (!this.persistenceProvider) { return null; } // Try to get info from LocalStoragePersistenceProvider if ('getStorageInfo' in this.persistenceProvider) { return this.persistenceProvider.getStorageInfo(); } return { provider: 'custom', available: true }; } } //# sourceMappingURL=ManneriDetector.js.map