brainy/dist/chat/BrainyChat.js
David Snelling f8c45f2d8d Initial commit: Brainy - Multi-Dimensional AI Database
Open source vector database with HNSW indexing, graph relationships,
and metadata facets. Features CLI with professional augmentation registry
integration for discovering extensions and capabilities.
2025-08-18 17:35:06 -07:00

368 lines
No EOL
13 KiB
JavaScript

/**
* BrainyChat - Magical Chat Command Center
*
* A smart chat system that leverages Brainy's standard noun/verb types
* to create intelligent, persistent conversations with automatic context loading.
*
* Key Features:
* - Uses standard NounType.Message for all chat messages
* - Employs VerbType.Communicates and VerbType.Precedes for conversation flow
* - Auto-discovery of previous sessions using Brainy's search capabilities
* - Hybrid architecture: basic chat (open source) + premium memory sync
*/
import { NounType, VerbType } from '../types/graphTypes.js';
/**
* Enhanced BrainyChat with automatic context loading and intelligent memory
*
* This extends basic chat functionality with premium features when available
*/
export class BrainyChat {
constructor(brainy) {
this.currentSessionId = null;
this.sessionCache = new Map();
this.brainy = brainy;
}
/**
* Initialize chat system and auto-discover last session
* Uses Brainy's advanced search to find the most recent conversation
*/
async initialize() {
try {
// Search for the most recent chat message using Brainy's search
const recentMessages = await this.brainy.search('recent chat conversation', 1, {
nounTypes: [NounType.Message],
metadata: {
messageType: 'chat'
}
});
if (recentMessages.length > 0) {
const lastMessage = recentMessages[0];
const sessionId = lastMessage.metadata?.sessionId;
if (sessionId) {
this.currentSessionId = sessionId;
return await this.loadSession(sessionId);
}
}
}
catch (error) {
console.debug('No previous session found, starting fresh:', error?.message);
}
return null;
}
/**
* Start a new chat session
* Automatically generates a session ID and stores session metadata
*/
async startNewSession(title, participants = ['user', 'assistant']) {
const sessionId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const session = {
id: sessionId,
title,
createdAt: new Date(),
lastMessageAt: new Date(),
messageCount: 0,
participants,
metadata: {
tags: ['active'],
premium: await this.isPremiumEnabled()
}
};
// Store session using BrainyData add() method
await this.brainy.add({
sessionType: 'chat',
title: title || `Chat Session ${new Date().toLocaleDateString()}`,
createdAt: session.createdAt.toISOString(),
lastMessageAt: session.lastMessageAt.toISOString(),
messageCount: session.messageCount,
participants: session.participants
}, {
id: sessionId,
nounType: NounType.Concept,
sessionType: 'chat'
});
this.currentSessionId = sessionId;
this.sessionCache.set(sessionId, session);
return session;
}
/**
* Add a message to the current session
* Stores using standard NounType.Message and creates conversation flow relationships
*/
async addMessage(content, speaker = 'user', metadata) {
if (!this.currentSessionId) {
await this.startNewSession();
}
const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const timestamp = new Date();
const message = {
id: messageId,
content,
speaker,
sessionId: this.currentSessionId,
timestamp,
metadata
};
// Store message using BrainyData add() method
await this.brainy.add({
messageType: 'chat',
content,
speaker,
sessionId: this.currentSessionId,
timestamp: timestamp.toISOString(),
...metadata
}, {
id: messageId,
nounType: NounType.Message,
messageType: 'chat',
sessionId: this.currentSessionId,
speaker
});
// Create relationships using standard verb types
await this.createMessageRelationships(messageId);
// Update session metadata
await this.updateSessionMetadata();
return message;
}
/**
* Get conversation history for current session
* Uses Brainy's graph traversal to get messages in chronological order
*/
async getHistory(limit = 50) {
if (!this.currentSessionId)
return [];
try {
// Search for messages in this session using Brainy's search
const messageNouns = await this.brainy.search('', // Empty query to get all messages
limit, {
nounTypes: [NounType.Message],
metadata: {
sessionId: this.currentSessionId,
messageType: 'chat'
}
});
return messageNouns.map((noun) => this.nounToChatMessage(noun));
}
catch (error) {
console.error('Error retrieving chat history:', error);
return [];
}
}
/**
* Search across all chat sessions and messages
* Leverages Brainy's powerful vector and semantic search
*/
async searchMessages(query, options) {
const metadata = {
messageType: 'chat'
};
if (options?.sessionId) {
metadata.sessionId = options.sessionId;
}
if (options?.speaker) {
metadata.speaker = options.speaker;
}
try {
const results = await this.brainy.search(options?.semanticSearch !== false ? query : '', options?.limit || 20, {
nounTypes: [NounType.Message],
metadata
});
return results.map((noun) => this.nounToChatMessage(noun));
}
catch (error) {
console.error('Error searching messages:', error);
return [];
}
}
/**
* Get all chat sessions
* Uses Brainy's search to find all conversation sessions
*/
async getSessions(limit = 20) {
try {
const sessionNouns = await this.brainy.search('', limit, {
nounTypes: [NounType.Concept],
metadata: {
sessionType: 'chat'
}
});
return sessionNouns.map((noun) => this.nounToChatSession(noun));
}
catch (error) {
console.error('Error retrieving sessions:', error);
return [];
}
}
/**
* Switch to a different session
* Automatically loads context and history
*/
async switchToSession(sessionId) {
try {
const session = await this.loadSession(sessionId);
if (session) {
this.currentSessionId = sessionId;
this.sessionCache.set(sessionId, session);
}
return session;
}
catch (error) {
console.error('Error switching to session:', error);
return null;
}
}
/**
* Archive a session (premium feature)
* Maintains full searchability while organizing conversations
*/
async archiveSession(sessionId) {
if (!await this.isPremiumEnabled()) {
throw new Error('Session archiving requires premium Brain Cloud subscription');
}
try {
// Since BrainyData doesn't have update, add an archive marker
await this.brainy.add({
archivedSessionId: sessionId,
archivedAt: new Date().toISOString(),
action: 'archive'
}, {
nounType: NounType.State,
sessionId,
archived: true
});
return true;
}
catch (error) {
console.error('Error archiving session:', error);
}
return false;
}
/**
* Generate session summary using AI (premium feature)
* Intelligently summarizes long conversations
*/
async generateSessionSummary(sessionId) {
if (!await this.isPremiumEnabled()) {
throw new Error('AI session summaries require premium Brain Cloud subscription');
}
try {
const messages = await this.getHistoryForSession(sessionId, 100);
const content = messages
.map(msg => `${msg.speaker}: ${msg.content}`)
.join('\n');
// Use Brainy's AI to generate summary (placeholder - would need actual AI integration)
const summaryResponse = `Summary of ${messages.length} messages discussing various topics in ${sessionId}`;
return summaryResponse || null;
}
catch (error) {
console.error('Error generating session summary:', error);
return null;
}
}
// Private helper methods
async createMessageRelationships(messageId) {
// Link message to session using unified addVerb API
await this.brainy.addVerb(messageId, this.currentSessionId, VerbType.PartOf, {
relationship: 'message-in-session'
});
// Find previous message to create conversation flow using VerbType.Precedes
const previousMessages = await this.brainy.search('', 1, {
nounTypes: [NounType.Message],
metadata: {
sessionId: this.currentSessionId,
messageType: 'chat'
}
});
if (previousMessages.length > 0 && previousMessages[0].id !== messageId) {
await this.brainy.addVerb(previousMessages[0].id, messageId, VerbType.Precedes, {
relationship: 'message-sequence'
});
}
}
async loadSession(sessionId) {
try {
const sessionNouns = await this.brainy.search('', 1, {
nounTypes: [NounType.Concept],
metadata: {
sessionType: 'chat'
}
});
// Filter by session ID manually since BrainyData search may not support ID filtering
const matchingSession = sessionNouns.find(noun => noun.id === sessionId);
if (matchingSession) {
return this.nounToChatSession(matchingSession);
}
}
catch (error) {
console.error('Error loading session:', error);
}
return null;
}
async getHistoryForSession(sessionId, limit = 50) {
try {
const messageNouns = await this.brainy.search('', limit, {
nounTypes: [NounType.Message],
metadata: {
sessionId: sessionId,
messageType: 'chat'
}
});
return messageNouns.map((noun) => this.nounToChatMessage(noun));
}
catch (error) {
console.error('Error retrieving session history:', error);
return [];
}
}
async updateSessionMetadata() {
if (!this.currentSessionId)
return;
// Since BrainyData doesn't have update functionality, we'll skip this
// In a real implementation, you'd need update capabilities
console.debug('Session metadata update skipped - BrainyData lacks update API');
}
nounToChatMessage(noun) {
return {
id: noun.id,
content: noun.metadata?.content || noun.data?.content || '',
speaker: noun.metadata?.speaker || noun.data?.speaker || 'unknown',
sessionId: noun.metadata?.sessionId || noun.data?.sessionId || '',
timestamp: new Date(noun.metadata?.timestamp || noun.data?.timestamp || Date.now()),
metadata: noun.metadata
};
}
nounToChatSession(noun) {
return {
id: noun.id,
title: noun.metadata?.title || noun.data?.title || 'Untitled Session',
createdAt: new Date(noun.metadata?.createdAt || noun.data?.createdAt || Date.now()),
lastMessageAt: new Date(noun.metadata?.lastMessageAt || noun.data?.lastMessageAt || Date.now()),
messageCount: noun.metadata?.messageCount || noun.data?.messageCount || 0,
participants: noun.metadata?.participants || noun.data?.participants || ['user', 'assistant'],
metadata: noun.metadata
};
}
toTimestamp(date) {
const seconds = Math.floor(date.getTime() / 1000);
const nanoseconds = (date.getTime() % 1000) * 1000000;
return { seconds, nanoseconds };
}
async isPremiumEnabled() {
// Check if premium augmentations are available
// This would integrate with the license validation system
try {
const augmentations = await this.brainy.listAugmentations();
return augmentations.some((aug) => aug.premium === true && aug.enabled === true);
}
catch {
return false;
}
}
// Public API methods for CLI integration
getCurrentSessionId() {
return this.currentSessionId;
}
getCurrentSession() {
return this.currentSessionId ? this.sessionCache.get(this.currentSessionId) || null : null;
}
}
//# sourceMappingURL=BrainyChat.js.map