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.
This commit is contained in:
commit
f8c45f2d8d
448 changed files with 103294 additions and 0 deletions
498
src/chat/BrainyChat.ts
Normal file
498
src/chat/BrainyChat.ts
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
/**
|
||||
* 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 { 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
|
||||
premium?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced BrainyChat with automatic context loading and intelligent memory
|
||||
*
|
||||
* This extends basic chat functionality with premium features when available
|
||||
*/
|
||||
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',
|
||||
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: 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'],
|
||||
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: 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
|
||||
}
|
||||
|
||||
// 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: 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
|
||||
limit,
|
||||
{
|
||||
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 : '',
|
||||
options?.limit || 20,
|
||||
{
|
||||
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(
|
||||
'',
|
||||
limit,
|
||||
{
|
||||
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 (premium feature)
|
||||
* Maintains full searchability while organizing conversations
|
||||
*/
|
||||
async archiveSession(sessionId: string): Promise<boolean> {
|
||||
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: string): Promise<string | null> {
|
||||
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
|
||||
|
||||
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(
|
||||
'',
|
||||
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'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadSession(sessionId: string): Promise<ChatSession | null> {
|
||||
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
|
||||
}
|
||||
|
||||
private async getHistoryForSession(sessionId: string, limit: number = 50): Promise<ChatMessage[]> {
|
||||
try {
|
||||
const messageNouns = await this.brainy.search(
|
||||
'',
|
||||
limit,
|
||||
{
|
||||
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 }
|
||||
}
|
||||
|
||||
private async isPremiumEnabled(): Promise<boolean> {
|
||||
// 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: any) => aug.premium === true && aug.enabled === true)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
428
src/chat/ChatCLI.ts
Normal file
428
src/chat/ChatCLI.ts
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
/**
|
||||
* ChatCLI - Command Line Interface for BrainyChat
|
||||
*
|
||||
* Provides a magical chat experience through the Brainy CLI with:
|
||||
* - Auto-discovery of previous sessions
|
||||
* - Intelligent context loading
|
||||
* - Multi-agent coordination support
|
||||
* - Premium memory sync integration
|
||||
*/
|
||||
|
||||
import { BrainyChat, type ChatSession, type ChatMessage } from './BrainyChat.js'
|
||||
import { BrainyData } from '../brainyData.js'
|
||||
|
||||
// Simple color utility without external dependencies
|
||||
const colors = {
|
||||
cyan: (text: string) => `\x1b[36m${text}\x1b[0m`,
|
||||
green: (text: string) => `\x1b[32m${text}\x1b[0m`,
|
||||
yellow: (text: string) => `\x1b[33m${text}\x1b[0m`,
|
||||
blue: (text: string) => `\x1b[34m${text}\x1b[0m`,
|
||||
gray: (text: string) => `\x1b[90m${text}\x1b[0m`,
|
||||
red: (text: string) => `\x1b[31m${text}\x1b[0m`
|
||||
}
|
||||
|
||||
export class ChatCLI {
|
||||
private brainyChat: BrainyChat
|
||||
private brainy: BrainyData
|
||||
|
||||
constructor(brainy: BrainyData) {
|
||||
this.brainy = brainy
|
||||
this.brainyChat = new BrainyChat(brainy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an interactive chat session
|
||||
* Automatically discovers and loads previous context
|
||||
*/
|
||||
async startInteractiveChat(options?: {
|
||||
sessionId?: string
|
||||
speaker?: string
|
||||
memory?: boolean
|
||||
newSession?: boolean
|
||||
}): Promise<void> {
|
||||
console.log(colors.cyan('🧠 Brainy Chat - Local Memory & Intelligence'))
|
||||
console.log()
|
||||
|
||||
let session: ChatSession | null = null
|
||||
|
||||
if (options?.sessionId) {
|
||||
// Load specific session
|
||||
session = await this.brainyChat.switchToSession(options.sessionId)
|
||||
if (session) {
|
||||
console.log(colors.green(`📂 Loaded session: ${session.title || session.id}`))
|
||||
console.log(colors.gray(` Created: ${session.createdAt.toLocaleDateString()}`))
|
||||
console.log(colors.gray(` Messages: ${session.messageCount}`))
|
||||
} else {
|
||||
console.log(colors.yellow(`⚠️ Session ${options.sessionId} not found, starting new session`))
|
||||
}
|
||||
} else if (!options?.newSession) {
|
||||
// Auto-discover last session
|
||||
console.log(colors.gray('🔍 Looking for your last conversation...'))
|
||||
session = await this.brainyChat.initialize()
|
||||
|
||||
if (session) {
|
||||
console.log(colors.green(`✨ Found your last session: ${session.title || 'Untitled'}`))
|
||||
console.log(colors.gray(` Last active: ${session.lastMessageAt.toLocaleString()}`))
|
||||
console.log(colors.gray(` Messages: ${session.messageCount}`))
|
||||
|
||||
// Show recent context if memory option is enabled
|
||||
if (options?.memory !== false) {
|
||||
await this.showRecentContext()
|
||||
}
|
||||
} else {
|
||||
console.log(colors.blue('🆕 No previous sessions found, starting fresh!'))
|
||||
}
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
session = await this.brainyChat.startNewSession(
|
||||
`Chat ${new Date().toLocaleDateString()}`,
|
||||
['user', options?.speaker || 'assistant']
|
||||
)
|
||||
console.log(colors.green(`🎉 Started new session: ${session.id}`))
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log(colors.gray('💡 Tips:'))
|
||||
console.log(colors.gray(' - Type /history to see conversation history'))
|
||||
console.log(colors.gray(' - Type /search <query> to search all conversations'))
|
||||
console.log(colors.gray(' - Type /sessions to list all sessions'))
|
||||
console.log(colors.gray(' - Type /help for more commands'))
|
||||
console.log(colors.gray(' - Type /quit to exit'))
|
||||
console.log()
|
||||
console.log(colors.blue('🚀 Want multi-agent coordination? Try: brainy cloud auth'))
|
||||
console.log()
|
||||
|
||||
// Start interactive loop
|
||||
await this.interactiveLoop(options?.speaker || 'assistant')
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single message and get response
|
||||
*/
|
||||
async sendMessage(
|
||||
message: string,
|
||||
options?: {
|
||||
sessionId?: string
|
||||
speaker?: string
|
||||
noResponse?: boolean
|
||||
}
|
||||
): Promise<ChatMessage[]> {
|
||||
if (options?.sessionId) {
|
||||
await this.brainyChat.switchToSession(options.sessionId)
|
||||
}
|
||||
|
||||
// Add user message
|
||||
const userMessage = await this.brainyChat.addMessage(message, 'user')
|
||||
console.log(colors.blue(`👤 You: ${message}`))
|
||||
|
||||
if (options?.noResponse) {
|
||||
return [userMessage]
|
||||
}
|
||||
|
||||
// For CLI usage, we'd integrate with whatever AI service is configured
|
||||
// This is a placeholder showing the architecture
|
||||
const response = await this.generateResponse(message, options?.speaker || 'assistant')
|
||||
const assistantMessage = await this.brainyChat.addMessage(
|
||||
response,
|
||||
options?.speaker || 'assistant',
|
||||
{
|
||||
model: 'claude-3-sonnet',
|
||||
context: { userMessage: userMessage.id }
|
||||
}
|
||||
)
|
||||
|
||||
console.log(colors.green(`🤖 ${options?.speaker || 'Assistant'}: ${response}`))
|
||||
|
||||
return [userMessage, assistantMessage]
|
||||
}
|
||||
|
||||
/**
|
||||
* Show conversation history
|
||||
*/
|
||||
async showHistory(limit: number = 10): Promise<void> {
|
||||
const messages = await this.brainyChat.getHistory(limit)
|
||||
|
||||
if (messages.length === 0) {
|
||||
console.log(colors.yellow('📭 No messages in current session'))
|
||||
return
|
||||
}
|
||||
|
||||
console.log(colors.cyan(`📜 Last ${Math.min(limit, messages.length)} messages:`))
|
||||
console.log()
|
||||
|
||||
for (const message of messages.slice(-limit)) {
|
||||
const timestamp = message.timestamp.toLocaleTimeString()
|
||||
const speakerColor = message.speaker === 'user' ? colors.blue : colors.green
|
||||
const icon = message.speaker === 'user' ? '👤' : '🤖'
|
||||
|
||||
console.log(speakerColor(`${icon} ${message.speaker} (${timestamp}):`))
|
||||
console.log(colors.gray(` ${message.content}`))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search across all conversations
|
||||
*/
|
||||
async searchConversations(
|
||||
query: string,
|
||||
options?: {
|
||||
limit?: number
|
||||
sessionId?: string
|
||||
semantic?: boolean
|
||||
}
|
||||
): Promise<void> {
|
||||
console.log(colors.cyan(`🔍 Searching for: "${query}"`))
|
||||
|
||||
const results = await this.brainyChat.searchMessages(query, {
|
||||
limit: options?.limit || 10,
|
||||
sessionId: options?.sessionId,
|
||||
semanticSearch: options?.semantic !== false
|
||||
})
|
||||
|
||||
if (results.length === 0) {
|
||||
console.log(colors.yellow('🤷 No matching messages found'))
|
||||
return
|
||||
}
|
||||
|
||||
console.log(colors.green(`✨ Found ${results.length} matches:`))
|
||||
console.log()
|
||||
|
||||
for (const message of results) {
|
||||
const date = message.timestamp.toLocaleDateString()
|
||||
const time = message.timestamp.toLocaleTimeString()
|
||||
const speakerColor = message.speaker === 'user' ? colors.blue : colors.green
|
||||
const icon = message.speaker === 'user' ? '👤' : '🤖'
|
||||
|
||||
console.log(colors.gray(`📅 ${date} ${time} - Session: ${message.sessionId.substring(0, 8)}...`))
|
||||
console.log(speakerColor(`${icon} ${message.speaker}: ${message.content}`))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all chat sessions
|
||||
*/
|
||||
async listSessions(): Promise<void> {
|
||||
const sessions = await this.brainyChat.getSessions()
|
||||
|
||||
if (sessions.length === 0) {
|
||||
console.log(colors.yellow('📭 No chat sessions found'))
|
||||
return
|
||||
}
|
||||
|
||||
console.log(colors.cyan(`💬 Your chat sessions (${sessions.length}):`))
|
||||
console.log()
|
||||
|
||||
for (const session of sessions) {
|
||||
const isActive = session.id === this.brainyChat.getCurrentSessionId()
|
||||
const activeIndicator = isActive ? colors.green(' ● ACTIVE') : ''
|
||||
const archived = session.metadata?.archived ? colors.gray(' [ARCHIVED]') : ''
|
||||
|
||||
console.log(colors.blue(`📂 ${session.title || 'Untitled'}${activeIndicator}${archived}`))
|
||||
console.log(colors.gray(` ID: ${session.id}`))
|
||||
console.log(colors.gray(` Created: ${session.createdAt.toLocaleDateString()}`))
|
||||
console.log(colors.gray(` Last active: ${session.lastMessageAt.toLocaleDateString()}`))
|
||||
console.log(colors.gray(` Messages: ${session.messageCount}`))
|
||||
console.log(colors.gray(` Participants: ${session.participants.join(', ')}`))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different session
|
||||
*/
|
||||
async switchSession(sessionId: string): Promise<void> {
|
||||
const session = await this.brainyChat.switchToSession(sessionId)
|
||||
|
||||
if (session) {
|
||||
console.log(colors.green(`✅ Switched to session: ${session.title || session.id}`))
|
||||
console.log(colors.gray(` Messages: ${session.messageCount}`))
|
||||
console.log(colors.gray(` Last active: ${session.lastMessageAt.toLocaleString()}`))
|
||||
} else {
|
||||
console.log(colors.red(`❌ Session ${sessionId} not found`))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help for chat commands
|
||||
*/
|
||||
showHelp(): void {
|
||||
console.log(colors.cyan('🧠 Brainy Chat Commands:'))
|
||||
console.log()
|
||||
console.log(colors.blue('Basic Commands:'))
|
||||
console.log(' /history [limit] - Show conversation history (default: 10 messages)')
|
||||
console.log(' /search <query> - Search all conversations')
|
||||
console.log(' /sessions - List all chat sessions')
|
||||
console.log(' /switch <id> - Switch to a specific session')
|
||||
console.log(' /new - Start a new session')
|
||||
console.log(' /help - Show this help')
|
||||
console.log(' /quit - Exit chat')
|
||||
console.log()
|
||||
|
||||
console.log(colors.yellow('Local Features:'))
|
||||
console.log(' ✨ Automatic session discovery')
|
||||
console.log(' 🧠 Local memory across all conversations')
|
||||
console.log(' 🔍 Semantic search using vector similarity')
|
||||
console.log(' 📊 Standard noun/verb graph relationships')
|
||||
console.log()
|
||||
|
||||
console.log(colors.green('Want More? Premium Features:'))
|
||||
console.log(' 🤝 Multi-agent coordination')
|
||||
console.log(' ☁️ Cross-device memory sync')
|
||||
console.log(' 🎨 Rich web coordination UI')
|
||||
console.log(' 🔄 Real-time team collaboration')
|
||||
console.log()
|
||||
console.log(colors.blue('Get premium: brainy cloud auth'))
|
||||
console.log()
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
private async interactiveLoop(assistantSpeaker: string = 'assistant'): Promise<void> {
|
||||
const readline = require('readline')
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
const askQuestion = (): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(colors.blue('💬 You: '), resolve)
|
||||
})
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const input = await askQuestion()
|
||||
|
||||
if (input.trim() === '') continue
|
||||
|
||||
// Handle commands
|
||||
if (input.startsWith('/')) {
|
||||
const [command, ...args] = input.slice(1).split(' ')
|
||||
|
||||
switch (command.toLowerCase()) {
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
console.log(colors.cyan('👋 Thanks for chatting! Your conversation is saved.'))
|
||||
rl.close()
|
||||
return
|
||||
|
||||
case 'history':
|
||||
const limit = args[0] ? parseInt(args[0]) : 10
|
||||
await this.showHistory(limit)
|
||||
break
|
||||
|
||||
case 'search':
|
||||
if (args.length === 0) {
|
||||
console.log(colors.yellow('Usage: /search <query>'))
|
||||
} else {
|
||||
await this.searchConversations(args.join(' '))
|
||||
}
|
||||
break
|
||||
|
||||
case 'sessions':
|
||||
await this.listSessions()
|
||||
break
|
||||
|
||||
case 'switch':
|
||||
if (args.length === 0) {
|
||||
console.log(colors.yellow('Usage: /switch <session-id>'))
|
||||
} else {
|
||||
await this.switchSession(args[0])
|
||||
}
|
||||
break
|
||||
|
||||
case 'new':
|
||||
const newSession = await this.brainyChat.startNewSession(
|
||||
`Chat ${new Date().toLocaleDateString()}`
|
||||
)
|
||||
console.log(colors.green(`🆕 Started new session: ${newSession.id}`))
|
||||
break
|
||||
|
||||
case 'archive':
|
||||
const sessionToArchive = args[0] || this.brainyChat.getCurrentSessionId()
|
||||
if (sessionToArchive) {
|
||||
try {
|
||||
await this.brainyChat.archiveSession(sessionToArchive)
|
||||
console.log(colors.green(`📁 Session archived: ${sessionToArchive}`))
|
||||
} catch (error: any) {
|
||||
console.log(colors.red(`❌ ${error?.message}`))
|
||||
}
|
||||
} else {
|
||||
console.log(colors.yellow('No session to archive'))
|
||||
}
|
||||
break
|
||||
|
||||
case 'summary':
|
||||
const sessionToSummarize = args[0] || this.brainyChat.getCurrentSessionId()
|
||||
if (sessionToSummarize) {
|
||||
try {
|
||||
const summary = await this.brainyChat.generateSessionSummary(sessionToSummarize)
|
||||
if (summary) {
|
||||
console.log(colors.green('📋 Session Summary:'))
|
||||
console.log(colors.gray(summary))
|
||||
} else {
|
||||
console.log(colors.yellow('No summary could be generated'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(colors.red(`❌ ${error?.message}`))
|
||||
}
|
||||
} else {
|
||||
console.log(colors.yellow('No session to summarize'))
|
||||
}
|
||||
break
|
||||
|
||||
case 'help':
|
||||
this.showHelp()
|
||||
break
|
||||
|
||||
default:
|
||||
console.log(colors.yellow(`Unknown command: ${command}`))
|
||||
console.log(colors.gray('Type /help for available commands'))
|
||||
}
|
||||
} else {
|
||||
// Regular message
|
||||
await this.sendMessage(input, { speaker: assistantSpeaker })
|
||||
}
|
||||
|
||||
console.log()
|
||||
} catch (error: any) {
|
||||
console.error(colors.red(`Error: ${error?.message}`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async showRecentContext(limit: number = 3): Promise<void> {
|
||||
const recentMessages = await this.brainyChat.getHistory(limit)
|
||||
|
||||
if (recentMessages.length > 0) {
|
||||
console.log(colors.gray('💭 Recent context:'))
|
||||
for (const msg of recentMessages.slice(-limit)) {
|
||||
const preview = msg.content.length > 60
|
||||
? msg.content.substring(0, 60) + '...'
|
||||
: msg.content
|
||||
console.log(colors.gray(` ${msg.speaker}: ${preview}`))
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
private async generateResponse(message: string, speaker: string): Promise<string> {
|
||||
// This is a placeholder for AI integration
|
||||
// In a real implementation, this would call the configured AI service
|
||||
// and could include multi-agent coordination
|
||||
|
||||
// Example responses for demonstration
|
||||
const responses = [
|
||||
"I remember our conversation and can help with that!",
|
||||
"Based on our previous discussions, I think...",
|
||||
"Let me search through our chat history for relevant context.",
|
||||
"I can coordinate with other AI agents if needed for this task."
|
||||
]
|
||||
|
||||
return responses[Math.floor(Math.random() * responses.length)]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue