@aituber-onair/manneri
Version:
A lightweight conversation pattern detection library to prevent repetitive AI responses
251 lines • 9.48 kB
JavaScript
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