c9ai
Version:
Universal AI assistant with vibe-based workflows, hybrid cloud+local AI, and comprehensive tool integration
244 lines (210 loc) • 6.87 kB
JavaScript
;
/**
* Context Manager - Maintains conversation context for mathematical queries
*/
class ContextManager {
constructor() {
this.sessions = new Map(); // sessionId -> context
this.maxSessions = 100;
this.sessionTimeout = 30 * 60 * 1000; // 30 minutes
}
/**
* Get or create session context
*/
getSession(sessionId) {
if (!this.sessions.has(sessionId)) {
this.sessions.set(sessionId, {
id: sessionId,
created: Date.now(),
lastAccessed: Date.now(),
messages: [],
variables: {},
pendingContext: null, // For incomplete problems
mathMode: false
});
}
const session = this.sessions.get(sessionId);
session.lastAccessed = Date.now();
return session;
}
/**
* Add message to session context
*/
addMessage(sessionId, role, content, metadata = {}) {
const session = this.getSession(sessionId);
session.messages.push({
role,
content,
timestamp: Date.now(),
...metadata
});
// Keep only last 20 messages for efficiency
if (session.messages.length > 20) {
session.messages = session.messages.slice(-20);
}
return session;
}
/**
* Set pending context for incomplete problems
*/
setPendingContext(sessionId, context) {
const session = this.getSession(sessionId);
session.pendingContext = context;
session.mathMode = true;
return session;
}
/**
* Get pending context and clear it
*/
consumePendingContext(sessionId) {
const session = this.getSession(sessionId);
const context = session.pendingContext;
session.pendingContext = null;
return context;
}
/**
* Check if query is continuing a previous math problem
*/
isContinuation(sessionId, query) {
const session = this.getSession(sessionId);
if (!session.pendingContext) return false;
// Look for continuation indicators
const continuationPatterns = [
/^(it is|that is|the other|second|height|width|base)\s+\d+/i,
/^\d+\s*(cm|m|inches?|ft|feet)/i,
/^(and|also|then)\s+/i,
// Diagonal measurements for shapes (most specific first)
/d1\s*=.*d2\s*=/i, // Removed ^ anchor to be more flexible
/^(diagonal|diagonals?)\s+/i,
/^(first|second)\s+(diagonal|side)\s+/i,
// Multiple measurements with units
/\d+\s*(cm|m|mm|inches?|ft)\s*(and|,)\s*\d+\s*(cm|m|mm|inches?|ft)/i,
// Variable assignments
/^\w+\s*=.*\w+\s*=/i,
/^(length|width|height|radius)\s*[:=]/i,
// "Sorry" corrections
/^sorry.*here.*right.*values?/i,
/^here.*right.*values?/i,
/^correct.*values?/i
];
return continuationPatterns.some(pattern => pattern.test(query.trim()));
}
/**
* Build contextual prompt for math problems
*/
buildContextualPrompt(sessionId, currentQuery) {
const session = this.getSession(sessionId);
if (this.isContinuation(sessionId, currentQuery)) {
const context = session.pendingContext;
if (context) {
// Combine previous context with current input
const contextualPrompt = `${context.problem} ${currentQuery}`;
// Clear pending context since we're now complete
session.pendingContext = null;
session.mathMode = false;
return {
isContextual: true,
originalQuery: currentQuery,
contextualQuery: contextualPrompt,
context: context
};
}
}
return {
isContextual: false,
originalQuery: currentQuery,
contextualQuery: currentQuery
};
}
/**
* Detect incomplete math problems that need more information
*/
detectIncompleteProblems(query, aiResponse) {
// Patterns that indicate incomplete problems
const incompletePatterns = [
/need(s?)\s+(to know|the length|more information|another)/i,
/cannot\s+(determine|calculate)\s+without/i,
/would need\s+the\s+(length|value|measurement)/i,
/please\s+provide\s+(the|more)/i,
/missing\s+(information|data|measurement)/i,
// Geometry-specific patterns
/need.*diagonal/i,
/provide.*diagonal/i,
/additional\s+information/i,
/without.*information/i,
/given\s+information/i
];
const isIncomplete = incompletePatterns.some(pattern =>
pattern.test(aiResponse)
);
if (isIncomplete) {
// Extract the problem context
return {
isIncomplete: true,
problem: query,
missingInfo: this.extractMissingInfo(aiResponse)
};
}
return { isIncomplete: false };
}
/**
* Extract what information is missing from AI response
*/
extractMissingInfo(response) {
const patterns = [
{ pattern: /(base|height|width|length|side)/gi, type: 'dimension' },
{ pattern: /(angle|degree)/gi, type: 'angle' },
{ pattern: /(rate|percentage)/gi, type: 'rate' },
{ pattern: /(time|period|duration)/gi, type: 'time' }
];
const missing = [];
patterns.forEach(({ pattern, type }) => {
const matches = response.match(pattern);
if (matches) {
missing.push(...matches.map(m => ({ term: m, type })));
}
});
return missing;
}
/**
* Clean up old sessions
*/
cleanup() {
const now = Date.now();
const toDelete = [];
for (const [sessionId, session] of this.sessions.entries()) {
if (now - session.lastAccessed > this.sessionTimeout) {
toDelete.push(sessionId);
}
}
toDelete.forEach(id => this.sessions.delete(id));
// Also limit total sessions
if (this.sessions.size > this.maxSessions) {
const sorted = Array.from(this.sessions.entries())
.sort(([,a], [,b]) => a.lastAccessed - b.lastAccessed);
const excess = this.sessions.size - this.maxSessions;
for (let i = 0; i < excess; i++) {
this.sessions.delete(sorted[i][0]);
}
}
}
/**
* Get session statistics
*/
getStats() {
return {
activeSessions: this.sessions.size,
sessionsWithPendingContext: Array.from(this.sessions.values())
.filter(s => s.pendingContext).length,
oldestSession: Math.min(...Array.from(this.sessions.values())
.map(s => s.created))
};
}
}
// Global context manager instance
const globalContextManager = new ContextManager();
// Cleanup every 5 minutes
setInterval(() => {
globalContextManager.cleanup();
}, 5 * 60 * 1000);
module.exports = { ContextManager, globalContextManager };