2025-08-26 12:32:21 -07:00
/ * *
* 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
* - Full - featured chat with memory and context management
* /
import { BrainyData } from '../brainyData.js'
import { NounType , VerbType , type Message , type GraphNoun , type GraphVerb } from '../types/graphTypes.js'
export interface ChatMessage {
id : string
content : string
speaker : 'user' | 'assistant' | string // Allow custom speaker names for multi-agent
sessionId : string
timestamp : Date
metadata ? : {
model? : string
usage ? : {
prompt_tokens? : number
completion_tokens? : number
}
context? : Record < string , any >
}
}
export interface ChatSession {
id : string
title? : string
createdAt : Date
lastMessageAt : Date
messageCount : number
participants : string [ ]
metadata ? : {
tags? : string [ ]
summary? : string
archived? : boolean
}
}
/ * *
* BrainyChat with automatic context loading and intelligent memory
*
* Full - featured chat functionality with conversation persistence
* /
export class BrainyChat {
private brainy : BrainyData
private currentSessionId : string | null = null
private sessionCache = new Map < string , ChatSession > ( )
constructor ( brainy : BrainyData ) {
this . brainy = brainy
}
/ * *
* Initialize chat system and auto - discover last session
* Uses Brainy ' s advanced search to find the most recent conversation
* /
async initialize ( ) : Promise < ChatSession | null > {
try {
// Search for the most recent chat message using Brainy's search
const recentMessages = await this . brainy . search (
'recent chat conversation' ,
{
2025-08-26 12:49:31 -07:00
limit : 1 ,
2025-08-26 12:32:21 -07:00
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 : any ) {
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? : string , participants : string [ ] = [ 'user' , 'assistant' ] ) : Promise < ChatSession > {
const sessionId = ` chat- ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } `
const session : ChatSession = {
id : sessionId ,
title ,
createdAt : new Date ( ) ,
lastMessageAt : new Date ( ) ,
messageCount : 0 ,
participants ,
metadata : {
tags : [ 'active' ]
}
}
2025-09-01 09:37:36 -07:00
// Store session using BrainyData addNoun() method
await this . brainy . addNoun (
2025-08-26 12:32:21 -07:00
{
sessionType : 'chat' ,
title : title || ` Chat Session ${ new Date ( ) . toLocaleDateString ( ) } ` ,
createdAt : session.createdAt.toISOString ( ) ,
lastMessageAt : session.lastMessageAt.toISOString ( ) ,
messageCount : session.messageCount ,
participants : session.participants
} ,
2025-09-01 09:37:36 -07:00
NounType . Concept , // Chat sessions are concepts
2025-08-26 12:32:21 -07:00
{
id : sessionId ,
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 : string ,
speaker : string = 'user' ,
metadata? : ChatMessage [ 'metadata' ]
) : Promise < ChatMessage > {
if ( ! this . currentSessionId ) {
await this . startNewSession ( )
}
const messageId = ` msg- ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } `
const timestamp = new Date ( )
const message : ChatMessage = {
id : messageId ,
content ,
speaker ,
sessionId : this.currentSessionId ! ,
timestamp ,
metadata
}
2025-09-01 09:37:36 -07:00
// Store message using BrainyData addNoun() method
await this . brainy . addNoun (
2025-08-26 12:32:21 -07:00
{
messageType : 'chat' ,
content ,
speaker ,
sessionId : this.currentSessionId ! ,
timestamp : timestamp.toISOString ( ) ,
. . . metadata
} ,
2025-09-01 09:37:36 -07:00
NounType . Message , // Chat messages are Message type
2025-08-26 12:32:21 -07:00
{
id : messageId ,
messageType : 'chat' ,
sessionId : this.currentSessionId ! ,
speaker
}
)
// Create relationships using standard verb types
await this . createMessageRelationships ( messageId )
// Update session metadata
await this . updateSessionMetadata ( )
return message
}
/ * *
* Ask a question and get a template - based response
* This provides basic functionality without requiring an LLM
* /
async ask ( question : string , options ? : {
includeSources? : boolean
maxSources? : number
sessionId? : string
} ) : Promise < string > {
// Add the user's question to the chat
await this . addMessage ( question , 'user' )
// Search for relevant content using Brainy's search
2025-08-26 12:49:31 -07:00
const searchResults = await this . brainy . search ( question , {
limit : options?.maxSources || 5
} )
2025-08-26 12:32:21 -07:00
// Generate a template-based response
let response = ''
if ( searchResults . length === 0 ) {
response = "I don't have enough information to answer that question based on the current data."
} else {
// Check if this is a count question
if ( question . toLowerCase ( ) . includes ( 'how many' ) || question . toLowerCase ( ) . includes ( 'count' ) ) {
response = ` Based on the search results, I found ${ searchResults . length } relevant items. `
}
// Check if this is a list question
else if ( question . toLowerCase ( ) . includes ( 'list' ) || question . toLowerCase ( ) . includes ( 'show me' ) ) {
response = ` Here are the relevant items I found: \ n ${ searchResults . map ( ( r , i ) = > ` ${ i + 1 } . ${ r . metadata ? . title || r . metadata ? . content || r . id } ` ) . join ( '\n' ) } `
}
// General question
else {
response = ` Based on the available data, I found information related to your question. The most relevant content includes: ${ searchResults [ 0 ] . metadata ? . title || searchResults [ 0 ] . metadata ? . content || searchResults [ 0 ] . id } `
}
// Add sources if requested
if ( options ? . includeSources && searchResults . length > 0 ) {
response += '\n\nSources: ' + searchResults . map ( r = > r . id ) . join ( ', ' )
}
}
// Add the assistant's response to the chat
await this . addMessage ( response , 'assistant' )
return response
}
/ * *
* Get conversation history for current session
* Uses Brainy ' s graph traversal to get messages in chronological order
* /
async getHistory ( limit : number = 50 ) : Promise < ChatMessage [ ] > {
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
{
2025-08-26 12:49:31 -07:00
limit : limit ,
2025-08-26 12:32:21 -07:00
nounTypes : [ NounType . Message ] ,
metadata : {
sessionId : this.currentSessionId ,
messageType : 'chat'
}
}
)
return messageNouns . map ( ( noun : any ) = > 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 : string ,
options ? : {
sessionId? : string
speaker? : string
limit? : number
semanticSearch? : boolean
}
) : Promise < ChatMessage [ ] > {
const metadata : Record < string , any > = {
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 : '' ,
{
2025-08-26 12:49:31 -07:00
limit : options?.limit || 20 ,
2025-08-26 12:32:21 -07:00
nounTypes : [ NounType . Message ] ,
metadata
}
)
return results . map ( ( noun : any ) = > 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 : number = 20 ) : Promise < ChatSession [ ] > {
try {
const sessionNouns = await this . brainy . search (
'' ,
{
2025-08-26 12:49:31 -07:00
limit : limit ,
2025-08-26 12:32:21 -07:00
nounTypes : [ NounType . Concept ] ,
metadata : {
sessionType : 'chat'
}
}
)
return sessionNouns . map ( ( noun : any ) = > 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 : string ) : Promise < ChatSession | null > {
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
* Maintains full searchability while organizing conversations
* /
async archiveSession ( sessionId : string ) : Promise < boolean > {
try {
// Since BrainyData doesn't have update, add an archive marker
2025-09-01 09:37:36 -07:00
await this . brainy . addNoun (
2025-08-26 12:32:21 -07:00
{
archivedSessionId : sessionId ,
archivedAt : new Date ( ) . toISOString ( ) ,
action : 'archive'
} ,
2025-09-01 09:37:36 -07:00
NounType . State , // Archive markers are State
2025-08-26 12:32:21 -07:00
{
sessionId ,
archived : true
}
)
return true
} catch ( error ) {
console . error ( 'Error archiving session:' , error )
}
return false
}
/ * *
* Generate session summary
* Creates a simple summary of the conversation
* For AI summaries , users can integrate their own LLM
* /
async generateSessionSummary ( sessionId : string ) : Promise < string | null > {
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
private async createMessageRelationships ( messageId : string ) : Promise < void > {
// 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 (
'' ,
{
2025-08-26 12:49:31 -07:00
limit : 1 ,
2025-08-26 12:32:21 -07:00
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'
}
)
}
}
private async loadSession ( sessionId : string ) : Promise < ChatSession | null > {
try {
const sessionNouns = await this . brainy . search (
'' ,
{
2025-08-26 12:49:31 -07:00
limit : 1 ,
2025-08-26 12:32:21 -07:00
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
}
private async getHistoryForSession ( sessionId : string , limit : number = 50 ) : Promise < ChatMessage [ ] > {
try {
const messageNouns = await this . brainy . search (
'' ,
{
2025-08-26 12:49:31 -07:00
limit : limit ,
2025-08-26 12:32:21 -07:00
nounTypes : [ NounType . Message ] ,
metadata : {
sessionId : sessionId ,
messageType : 'chat'
}
}
)
return messageNouns . map ( ( noun : any ) = > this . nounToChatMessage ( noun ) )
} catch ( error ) {
console . error ( 'Error retrieving session history:' , error )
return [ ]
}
}
private async updateSessionMetadata ( ) : Promise < void > {
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' )
}
private nounToChatMessage ( noun : any ) : ChatMessage {
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
}
}
private nounToChatSession ( noun : any ) : ChatSession {
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
}
}
private toTimestamp ( date : Date ) : { seconds : number ; nanoseconds : number } {
const seconds = Math . floor ( date . getTime ( ) / 1000 )
const nanoseconds = ( date . getTime ( ) % 1000 ) * 1000000
return { seconds , nanoseconds }
}
// Public API methods for CLI integration
getCurrentSessionId ( ) : string | null {
return this . currentSessionId
}
getCurrentSession ( ) : ChatSession | null {
return this . currentSessionId ? this . sessionCache . get ( this . currentSessionId ) || null : null
}
}