Impelmented inital memory system
This commit is contained in:
		| @ -27,6 +27,7 @@ model User { | ||||
|   id              Int       @id @default(autoincrement()) | ||||
|   userFqn         String    @unique | ||||
|   lastRespondedTo DateTime? | ||||
|   memory          UserMemory? | ||||
| } | ||||
|  | ||||
| model Reaction { | ||||
| @ -38,4 +39,37 @@ model Reaction { | ||||
|    | ||||
|   @@unique([statusId]) // Prevent multiple reactions to same status | ||||
|   @@map("reactions") | ||||
| } | ||||
|  | ||||
| model UserMemory { | ||||
|   id                      Int      @id @default(autoincrement()) | ||||
|   userFqn                String   @unique | ||||
|   personalityTraits       String   @default("[]") // JSON string of personality observations | ||||
|   runningGags            String   @default("[]") // JSON string of running jokes/gags | ||||
|   relationships          String   @default("[]") // JSON string of relationship dynamics with bot | ||||
|   interests              String   @default("[]") // JSON string of user interests | ||||
|   backstory              String   @default("[]") // JSON string of biographical elements | ||||
|   lastInteractionSummary String?  // Brief summary of last chat | ||||
|   interactionCount       Int      @default(0) | ||||
|   lastUpdated           DateTime @default(now()) @updatedAt | ||||
|   createdAt             DateTime @default(now()) | ||||
|    | ||||
|   // Relation to existing User model | ||||
|   user User @relation(fields: [userFqn], references: [userFqn]) | ||||
|    | ||||
|   @@map("user_memories") | ||||
| } | ||||
|  | ||||
| model InteractionLog { | ||||
|   id              Int      @id @default(autoincrement()) | ||||
|   userFqn         String | ||||
|   conversationSnapshot String // Key parts of the conversation | ||||
|   sentiment       String   // positive, negative, teasing, etc. | ||||
|   extractedTopics String   @default("[]") // JSON string of topics discussed | ||||
|   memorableQuotes String   @default("[]") // JSON string of funny/notable quotes | ||||
|   botEmotionalState String? // How the bot should "feel" about this interaction | ||||
|   createdAt       DateTime @default(now()) | ||||
|    | ||||
|   @@map("interaction_logs") | ||||
|   @@index([userFqn, createdAt]) | ||||
| } | ||||
							
								
								
									
										154
									
								
								src/api.ts
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								src/api.ts
									
									
									
									
									
								
							| @ -1,6 +1,8 @@ | ||||
| import { envConfig, prisma } from "./main.js"; | ||||
| import { PleromaEmoji, Notification, ContextResponse } from "../types.js"; | ||||
| import { selectRandomEmojis } from "./util.js"; | ||||
| import { getUserMemory, parseJsonArray, stringifyJsonArray } from "./memory.js"; | ||||
|  | ||||
|  | ||||
|  | ||||
| const getNotifications = async () => { | ||||
| @ -272,6 +274,154 @@ const handlePostReaction = async (notification: Notification): Promise<void> => | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Get detailed user memory for admin/debugging | ||||
|  */ | ||||
| const getUserMemoryDetails = async (userFqn: string) => { | ||||
|   try { | ||||
|     const memory = await prisma.userMemory.findUnique({ | ||||
|       where: { userFqn: userFqn }, | ||||
|       include: { | ||||
|         user: true | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     if (!memory) return null; | ||||
|  | ||||
|     // Get recent interaction logs | ||||
|     const recentLogs = await prisma.interactionLog.findMany({ | ||||
|       where: { userFqn: userFqn }, | ||||
|       orderBy: { createdAt: 'desc' }, | ||||
|       take: 10 | ||||
|     }); | ||||
|  | ||||
|     // Parse JSON strings for better readability | ||||
|     const parsedMemory = { | ||||
|       ...memory, | ||||
|       personalityTraits: parseJsonArray(memory.personalityTraits), | ||||
|       runningGags: parseJsonArray(memory.runningGags), | ||||
|       relationships: parseJsonArray(memory.relationships), | ||||
|       interests: parseJsonArray(memory.interests), | ||||
|       backstory: parseJsonArray(memory.backstory), | ||||
|       recentInteractions: recentLogs.map(log => ({ | ||||
|         ...log, | ||||
|         extractedTopics: parseJsonArray(log.extractedTopics), | ||||
|         memorableQuotes: parseJsonArray(log.memorableQuotes) | ||||
|       })) | ||||
|     }; | ||||
|  | ||||
|     return parsedMemory; | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error getting user memory details: ${error.message}`); | ||||
|     return null; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Manually add or remove memory elements (for admin use) | ||||
|  */ | ||||
| const modifyUserMemory = async ( | ||||
|   userFqn: string,  | ||||
|   action: 'add' | 'remove', | ||||
|   category: 'personalityTraits' | 'runningGags' | 'relationships' | 'interests' | 'backstory', | ||||
|   item: string | ||||
| ) => { | ||||
|   try { | ||||
|     const memory = await getUserMemory(userFqn); | ||||
|     if (!memory) return false; | ||||
|  | ||||
|     const currentArray = parseJsonArray(memory[category] as string); | ||||
|     let updatedArray: string[]; | ||||
|  | ||||
|     if (action === 'add') { | ||||
|       updatedArray = [...new Set([...currentArray, item])]; // Add without duplicates | ||||
|     } else { | ||||
|       updatedArray = currentArray.filter(existingItem => existingItem !== item); | ||||
|     } | ||||
|  | ||||
|     await prisma.userMemory.update({ | ||||
|       where: { userFqn: userFqn }, | ||||
|       data: { [category]: stringifyJsonArray(updatedArray) } | ||||
|     }); | ||||
|  | ||||
|     console.log(`${action === 'add' ? 'Added' : 'Removed'} "${item}" ${action === 'add' ? 'to' : 'from'} ${category} for ${userFqn}`); | ||||
|     return true; | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error modifying user memory: ${error.message}`); | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| const getMemoryStats = async () => { | ||||
|   try { | ||||
|     const totalUsers = await prisma.userMemory.count(); | ||||
|     const totalInteractions = await prisma.interactionLog.count(); | ||||
|      | ||||
|     const mostActiveUsers = await prisma.userMemory.findMany({ | ||||
|       orderBy: { interactionCount: 'desc' }, | ||||
|       take: 10, | ||||
|       select: { | ||||
|         userFqn: true, | ||||
|         interactionCount: true, | ||||
|         personalityTraits: true, | ||||
|         runningGags: true | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Parse JSON strings for the active users | ||||
|     const parsedActiveUsers = mostActiveUsers.map(user => ({ | ||||
|       ...user, | ||||
|       personalityTraits: parseJsonArray(user.personalityTraits), | ||||
|       runningGags: parseJsonArray(user.runningGags) | ||||
|     })); | ||||
|  | ||||
|     const sentimentStats = await prisma.interactionLog.groupBy({ | ||||
|       by: ['sentiment'], | ||||
|       _count: { sentiment: true } | ||||
|     }); | ||||
|  | ||||
|     return { | ||||
|       totalUsers, | ||||
|       totalInteractions, | ||||
|       mostActiveUsers: parsedActiveUsers, | ||||
|       sentimentDistribution: sentimentStats | ||||
|     }; | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error getting memory stats: ${error.message}`); | ||||
|     return null; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const resetUserMemory = async (userFqn: string) => { | ||||
|   try { | ||||
|     await prisma.userMemory.update({ | ||||
|       where: { userFqn: userFqn }, | ||||
|       data: { | ||||
|         personalityTraits: stringifyJsonArray([]), | ||||
|         runningGags: stringifyJsonArray([]), | ||||
|         relationships: stringifyJsonArray([]), | ||||
|         interests: stringifyJsonArray([]), | ||||
|         backstory: stringifyJsonArray([]), | ||||
|         lastInteractionSummary: null, | ||||
|         interactionCount: 0, | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Optionally delete interaction logs too | ||||
|     await prisma.interactionLog.deleteMany({ | ||||
|       where: { userFqn: userFqn } | ||||
|     }); | ||||
|  | ||||
|     console.log(`Reset memory for ${userFqn}`); | ||||
|     return true; | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error resetting user memory: ${error.message}`); | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| export { | ||||
|   deleteNotification, | ||||
|   getInstanceEmojis, | ||||
| @ -280,4 +430,8 @@ export { | ||||
|   reactToStatus, | ||||
|   handlePostReaction, | ||||
|   hasAlreadyReacted, | ||||
|   getUserMemoryDetails, | ||||
|   modifyUserMemory, | ||||
|   getMemoryStats, | ||||
|   resetUserMemory, | ||||
| }; | ||||
|  | ||||
							
								
								
									
										50
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -26,6 +26,11 @@ import { | ||||
|   isLLMRefusal, | ||||
|   shouldContinue, | ||||
| } from "./util.js"; | ||||
| import { | ||||
|   analyzeInteraction, | ||||
|   updateUserMemory, | ||||
|   generateMemoryContext, | ||||
| } from "./memory.js"; | ||||
|  | ||||
| export const prisma = new PrismaClient(); | ||||
|  | ||||
| @ -85,6 +90,9 @@ const generateOllamaRequest = async ( | ||||
|       await recordPendingResponse(notification); | ||||
|       await storeUserData(notification); | ||||
|        | ||||
|       const userFqn = notification.status.account.fqn; | ||||
|       const userMessage = notification.status.pleroma.content["text/plain"]; | ||||
|        | ||||
|       let conversationHistory: PostAncestorsForModel[] = []; | ||||
|       if (replyWithContext) { | ||||
|         const contextPosts = await getStatusContext(notification.status.id); | ||||
| @ -101,19 +109,23 @@ const generateOllamaRequest = async ( | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|       const userMessage = `${notification.status.account.fqn} says: ${notification.status.pleroma.content["text/plain"]}`; | ||||
|       const formattedUserMessage = `${userFqn} says: ${userMessage}`; | ||||
|  | ||||
|       // Get user memory context | ||||
|       const memoryContext = await generateMemoryContext(userFqn); | ||||
|  | ||||
|       // Get random emojis for this request | ||||
|       const emojiList = await getInstanceEmojis(); | ||||
|       let availableEmojis = ""; | ||||
|       if (emojiList && emojiList.length > 0) { | ||||
|         const randomEmojis = selectRandomEmojis(emojiList, 20); | ||||
|         availableEmojis = `\n\nAvailable custom emojis you can use in your response (or use none!) (format as :emoji_name:): ${randomEmojis.join(", ")}`; | ||||
|         availableEmojis = `\n\nAvailable custom emojis you can use in your response (format as :emoji_name:): ${randomEmojis.join(", ")}`; | ||||
|       } | ||||
|  | ||||
|       let systemContent = ollamaSystemPrompt + availableEmojis; | ||||
|       let systemContent = ollamaSystemPrompt + memoryContext + availableEmojis; | ||||
|        | ||||
|       if (replyWithContext) { | ||||
|         systemContent = `${ollamaSystemPrompt}\n\nPrevious conversation context:\n${conversationHistory | ||||
|         systemContent = `${ollamaSystemPrompt}${memoryContext}\n\nPrevious conversation context:\n${conversationHistory | ||||
|           .map( | ||||
|             (post) => | ||||
|               `${post.account_fqn} (to ${post.mentions.join(", ")}): ${ | ||||
| @ -135,7 +147,7 @@ const generateOllamaRequest = async ( | ||||
|         model: ollamaModel, | ||||
|         messages: [ | ||||
|           { role: "system", content: systemContent as string }, | ||||
|           { role: "user", content: userMessage }, | ||||
|           { role: "user", content: formattedUserMessage }, | ||||
|         ], | ||||
|         stream: false, | ||||
|         options: currentConfig, | ||||
| @ -153,6 +165,9 @@ const generateOllamaRequest = async ( | ||||
|         return generateOllamaRequest(notification, retryAttempt + 1); | ||||
|       } | ||||
|  | ||||
|       // Analyze interaction and update user memory (async, don't block response) | ||||
|       analyzeAndUpdateMemory(userFqn, userMessage, ollamaResponse.message.content); | ||||
|  | ||||
|       await storePromptData(notification, ollamaResponse); | ||||
|       return ollamaResponse; | ||||
|     } | ||||
| @ -161,6 +176,31 @@ const generateOllamaRequest = async ( | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Analyze interaction and update user memory (runs asynchronously) | ||||
|  */ | ||||
| const analyzeAndUpdateMemory = async ( | ||||
|   userFqn: string,  | ||||
|   userMessage: string,  | ||||
|   botResponse: string | ||||
| ): Promise<void> => { | ||||
|   try { | ||||
|     // Run analysis in background - don't await to avoid blocking response | ||||
|     const analysis = await analyzeInteraction(userMessage, botResponse, userFqn); | ||||
|      | ||||
|     await updateUserMemory({ | ||||
|       userFqn, | ||||
|       conversationContent: userMessage, | ||||
|       botResponse, | ||||
|       analysis, | ||||
|     }); | ||||
|   } catch (error: any) { | ||||
|     console.error(`Memory analysis failed for ${userFqn}: ${error.message}`); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| const postReplyToStatus = async ( | ||||
|   notification: Notification, | ||||
|   ollamaResponseBody: OllamaChatResponse | ||||
|  | ||||
							
								
								
									
										284
									
								
								src/memory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								src/memory.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,284 @@ | ||||
| // Updated memory.ts with JSON string handling for SQLite | ||||
| import { prisma } from "./main.js"; | ||||
| import { envConfig } from "./main.js"; | ||||
| import { InteractionAnalysis, MemoryUpdateRequest, OllamaChatRequest, OllamaChatResponse } from "../types.js"; | ||||
|  | ||||
| // Helper functions for JSON string array handling | ||||
| const parseJsonArray = (jsonString: string): string[] => { | ||||
|   try { | ||||
|     const parsed = JSON.parse(jsonString); | ||||
|     return Array.isArray(parsed) ? parsed : []; | ||||
|   } catch { | ||||
|     return []; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const stringifyJsonArray = (array: string[]): string => { | ||||
|   return JSON.stringify(array); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Analyze a conversation to extract user personality, gags, and relationship dynamics | ||||
|  */ | ||||
| const analyzeInteraction = async ( | ||||
|   userMessage: string,  | ||||
|   botResponse: string,  | ||||
|   userFqn: string | ||||
| ): Promise<InteractionAnalysis> => { | ||||
|   const { ollamaUrl, ollamaModel } = envConfig; | ||||
|    | ||||
|   const analysisPrompt = `Analyze this conversation between a user and a cute female AI chatbot named Lexi. Extract personality traits, running gags, relationship dynamics, and interesting facts. | ||||
|  | ||||
| User (${userFqn}): ${userMessage} | ||||
| Bot (Lexi): ${botResponse} | ||||
|  | ||||
| Please analyze and respond with a JSON object containing: | ||||
| { | ||||
|   "sentiment": "positive|negative|neutral|teasing|flirty|aggressive", | ||||
|   "topics": ["topic1", "topic2"], | ||||
|   "personalityObservations": ["trait1", "trait2"], | ||||
|   "runningGagUpdates": ["gag1", "gag2"], | ||||
|   "relationshipUpdates": ["relationship_change1"], | ||||
|   "interestMentions": ["interest1", "interest2"], | ||||
|   "backstoryElements": ["fact1", "fact2"], | ||||
|   "memorableQuotes": ["quote1", "quote2"] | ||||
| } | ||||
|  | ||||
| Focus on: | ||||
| - Personality traits (sarcastic, teasing, protective, joker, etc.) | ||||
| - Running gags and memes (fake claims, recurring jokes, etc.) | ||||
| - How they treat the bot (mean, nice, flirty, protective) | ||||
| - Interests and hobbies mentioned | ||||
| - Any biographical info (real or fake "lore") | ||||
| - Memorable or funny quotes | ||||
|  | ||||
| Keep entries brief and specific. Empty arrays are fine if nothing notable.`; | ||||
|  | ||||
|   try { | ||||
|     const analysisRequest: OllamaChatRequest = { | ||||
|       model: ollamaModel, | ||||
|       messages: [ | ||||
|         {  | ||||
|           role: "system",  | ||||
|           content: "You are an expert at analyzing social interactions and extracting personality insights. Always respond with valid JSON only."  | ||||
|         }, | ||||
|         { role: "user", content: analysisPrompt } | ||||
|       ], | ||||
|       stream: false, | ||||
|       options: { | ||||
|         temperature: 0.3, // Lower temperature for more consistent analysis | ||||
|         num_predict: 800, | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     const response = await fetch(`${ollamaUrl}/api/chat`, { | ||||
|       method: "POST", | ||||
|       body: JSON.stringify(analysisRequest), | ||||
|     }); | ||||
|  | ||||
|     if (!response.ok) { | ||||
|       throw new Error(`Analysis request failed: ${response.statusText}`); | ||||
|     } | ||||
|  | ||||
|     const analysisResponse: OllamaChatResponse = await response.json(); | ||||
|      | ||||
|     try { | ||||
|       // Parse the JSON response | ||||
|       const analysis: InteractionAnalysis = JSON.parse(analysisResponse.message.content.trim()); | ||||
|       return analysis; | ||||
|     } catch (parseError) { | ||||
|       console.error("Failed to parse analysis JSON:", analysisResponse.message.content); | ||||
|       // Return default analysis if parsing fails | ||||
|       return { | ||||
|         sentiment: 'neutral', | ||||
|         topics: [], | ||||
|         personalityObservations: [], | ||||
|         runningGagUpdates: [], | ||||
|         relationshipUpdates: [], | ||||
|         interestMentions: [], | ||||
|         backstoryElements: [], | ||||
|         memorableQuotes: [] | ||||
|       }; | ||||
|     } | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error analyzing interaction: ${error.message}`); | ||||
|     return { | ||||
|       sentiment: 'neutral', | ||||
|       topics: [], | ||||
|       personalityObservations: [], | ||||
|       runningGagUpdates: [], | ||||
|       relationshipUpdates: [], | ||||
|       interestMentions: [], | ||||
|       backstoryElements: [], | ||||
|       memorableQuotes: [] | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Get or create user memory profile | ||||
|  */ | ||||
| const getUserMemory = async (userFqn: string) => { | ||||
|   try { | ||||
|     let memory = await prisma.userMemory.findUnique({ | ||||
|       where: { userFqn: userFqn } | ||||
|     }); | ||||
|  | ||||
|     if (!memory) { | ||||
|       memory = await prisma.userMemory.create({ | ||||
|         data: { | ||||
|           userFqn: userFqn, | ||||
|           personalityTraits: stringifyJsonArray([]), | ||||
|           runningGags: stringifyJsonArray([]), | ||||
|           relationships: stringifyJsonArray([]), | ||||
|           interests: stringifyJsonArray([]), | ||||
|           backstory: stringifyJsonArray([]), | ||||
|           lastInteractionSummary: null, | ||||
|           interactionCount: 0, | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return memory; | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error getting user memory: ${error.message}`); | ||||
|     return null; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Update user memory with new interaction insights | ||||
|  */ | ||||
| const updateUserMemory = async (request: MemoryUpdateRequest): Promise<void> => { | ||||
|   try { | ||||
|     const { userFqn, conversationContent, botResponse, analysis } = request; | ||||
|      | ||||
|     // Get existing memory | ||||
|     const existingMemory = await getUserMemory(userFqn); | ||||
|     if (!existingMemory) return; | ||||
|  | ||||
|     // Parse existing JSON arrays | ||||
|     const existingPersonality = parseJsonArray(existingMemory.personalityTraits); | ||||
|     const existingGags = parseJsonArray(existingMemory.runningGags); | ||||
|     const existingRelationships = parseJsonArray(existingMemory.relationships); | ||||
|     const existingInterests = parseJsonArray(existingMemory.interests); | ||||
|     const existingBackstory = parseJsonArray(existingMemory.backstory); | ||||
|  | ||||
|     // Merge new observations with existing ones (avoiding duplicates) | ||||
|     const mergeArrays = (existing: string[], newItems: string[]): string[] => { | ||||
|       const combined = [...existing, ...newItems]; | ||||
|       return [...new Set(combined)]; // Remove duplicates | ||||
|     }; | ||||
|  | ||||
|     // Limit array sizes to prevent memory bloat | ||||
|     const limitArray = (arr: string[], maxSize: number = 20): string[] => { | ||||
|       return arr.slice(-maxSize); // Keep most recent items | ||||
|     }; | ||||
|  | ||||
|     const updatedMemory = { | ||||
|       personalityTraits: stringifyJsonArray(limitArray(mergeArrays(existingPersonality, analysis.personalityObservations))), | ||||
|       runningGags: stringifyJsonArray(limitArray(mergeArrays(existingGags, analysis.runningGagUpdates))), | ||||
|       relationships: stringifyJsonArray(limitArray(mergeArrays(existingRelationships, analysis.relationshipUpdates))), | ||||
|       interests: stringifyJsonArray(limitArray(mergeArrays(existingInterests, analysis.interestMentions))), | ||||
|       backstory: stringifyJsonArray(limitArray(mergeArrays(existingBackstory, analysis.backstoryElements))), | ||||
|       lastInteractionSummary: `${analysis.sentiment} conversation about ${analysis.topics.join(', ') || 'general chat'}`, | ||||
|       interactionCount: existingMemory.interactionCount + 1, | ||||
|     }; | ||||
|  | ||||
|     // Update database | ||||
|     await prisma.userMemory.update({ | ||||
|       where: { userFqn: userFqn }, | ||||
|       data: updatedMemory | ||||
|     }); | ||||
|  | ||||
|     // Log the interaction for historical reference | ||||
|     await prisma.interactionLog.create({ | ||||
|       data: { | ||||
|         userFqn: userFqn, | ||||
|         conversationSnapshot: `${userFqn}: ${conversationContent.slice(0, 200)}... | Lexi: ${botResponse.slice(0, 200)}...`, | ||||
|         sentiment: analysis.sentiment, | ||||
|         extractedTopics: stringifyJsonArray(analysis.topics), | ||||
|         memorableQuotes: stringifyJsonArray(analysis.memorableQuotes), | ||||
|         botEmotionalState: generateEmotionalState(analysis), | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     console.log(`Updated memory for ${userFqn}: ${analysis.personalityObservations.join(', ')}`); | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error updating user memory: ${error.message}`); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Generate how the bot should "feel" about this interaction | ||||
|  */ | ||||
| const generateEmotionalState = (analysis: InteractionAnalysis): string => { | ||||
|   const { sentiment, relationshipUpdates } = analysis; | ||||
|    | ||||
|   if (sentiment === 'teasing') return 'playfully_hurt'; | ||||
|   if (sentiment === 'flirty') return 'flustered'; | ||||
|   if (sentiment === 'aggressive') return 'sad'; | ||||
|   if (relationshipUpdates.some(rel => rel.includes('hurt') || rel.includes('mean'))) return 'hurt_feelings'; | ||||
|   if (relationshipUpdates.some(rel => rel.includes('cute') || rel.includes('sweet'))) return 'happy'; | ||||
|   return 'neutral'; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Generate memory context for system prompt | ||||
|  */ | ||||
| const generateMemoryContext = async (userFqn: string): Promise<string> => { | ||||
|   try { | ||||
|     const memory = await getUserMemory(userFqn); | ||||
|     if (!memory || memory.interactionCount === 0) { | ||||
|       return ""; | ||||
|     } | ||||
|  | ||||
|     let context = `\n\n--- User Memory for ${userFqn} ---\n`; | ||||
|      | ||||
|     const personalityTraits = parseJsonArray(memory.personalityTraits); | ||||
|     const runningGags = parseJsonArray(memory.runningGags); | ||||
|     const relationships = parseJsonArray(memory.relationships); | ||||
|     const interests = parseJsonArray(memory.interests); | ||||
|     const backstory = parseJsonArray(memory.backstory); | ||||
|      | ||||
|     if (personalityTraits.length > 0) { | ||||
|       context += `Personality: ${personalityTraits.join(', ')}\n`; | ||||
|     } | ||||
|      | ||||
|     if (runningGags.length > 0) { | ||||
|       context += `Running gags: ${runningGags.join(', ')}\n`; | ||||
|     } | ||||
|      | ||||
|     if (relationships.length > 0) { | ||||
|       context += `Our relationship: ${relationships.join(', ')}\n`; | ||||
|     } | ||||
|      | ||||
|     if (interests.length > 0) { | ||||
|       context += `Interests: ${interests.join(', ')}\n`; | ||||
|     } | ||||
|      | ||||
|     if (backstory.length > 0) { | ||||
|       context += `Background: ${backstory.join(', ')}\n`; | ||||
|     } | ||||
|      | ||||
|     if (memory.lastInteractionSummary) { | ||||
|       context += `Last time we talked: ${memory.lastInteractionSummary}\n`; | ||||
|     } | ||||
|      | ||||
|     context += `Total conversations: ${memory.interactionCount}`; | ||||
|      | ||||
|     return context; | ||||
|   } catch (error: any) { | ||||
|     console.error(`Error generating memory context: ${error.message}`); | ||||
|     return ""; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   analyzeInteraction, | ||||
|   updateUserMemory, | ||||
|   getUserMemory, | ||||
|   generateMemoryContext, | ||||
|   parseJsonArray, | ||||
|   stringifyJsonArray, | ||||
| }; | ||||
							
								
								
									
										33
									
								
								types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								types.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -337,3 +337,36 @@ export interface OllamaConfigOptions { | ||||
|    */ | ||||
|   num_thread?: number; | ||||
| } | ||||
|  | ||||
|  | ||||
| export interface UserMemory { | ||||
|   id: number; | ||||
|   userFqn: string; | ||||
|   personalityTraits: string[]; // ["teases_bot", "sarcastic", "friendly", "joker"] | ||||
|   runningGags: string[]; // ["claims_to_shit_pants", "pretends_to_be_cat", "always_hungry"] | ||||
|   relationships: string[]; // ["hurt_my_feelings_once", "called_me_cute", "protective_of_me"] | ||||
|   interests: string[]; // ["programming", "anime", "cooking"] | ||||
|   backstory: string[]; // ["works_at_tech_company", "has_three_cats", "lives_in_california"] | ||||
|   lastInteractionSummary: string; // Brief summary of last conversation | ||||
|   interactionCount: number; | ||||
|   lastUpdated: DateTime; | ||||
|   createdAt: DateTime; | ||||
| } | ||||
|  | ||||
| export interface InteractionAnalysis { | ||||
|   sentiment: 'positive' | 'negative' | 'neutral' | 'teasing' | 'flirty' | 'aggressive'; | ||||
|   topics: string[]; // Extracted topics from conversation | ||||
|   personalityObservations: string[]; // New traits observed | ||||
|   runningGagUpdates: string[]; // New or updated running gags | ||||
|   relationshipUpdates: string[]; // How relationship with bot changed | ||||
|   interestMentions: string[]; // Interests/hobbies mentioned | ||||
|   backstoryElements: string[]; // New biographical info (real or fake) | ||||
|   memorableQuotes: string[]; // Funny or notable things they said | ||||
| } | ||||
|  | ||||
| export interface MemoryUpdateRequest { | ||||
|   userFqn: string; | ||||
|   conversationContent: string; | ||||
|   botResponse: string; | ||||
|   analysis: InteractionAnalysis; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user