libre-webui 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +204 -0
  3. package/backend/dist/db.d.ts +19 -0
  4. package/backend/dist/db.d.ts.map +1 -0
  5. package/backend/dist/db.js +355 -0
  6. package/backend/dist/db.js.map +1 -0
  7. package/backend/dist/env.d.ts +2 -0
  8. package/backend/dist/env.d.ts.map +1 -0
  9. package/backend/dist/env.js +22 -0
  10. package/backend/dist/env.js.map +1 -0
  11. package/backend/dist/index.d.ts +4 -0
  12. package/backend/dist/index.d.ts.map +1 -0
  13. package/backend/dist/index.js +751 -0
  14. package/backend/dist/index.js.map +1 -0
  15. package/backend/dist/middleware/auth.d.ts +18 -0
  16. package/backend/dist/middleware/auth.d.ts.map +1 -0
  17. package/backend/dist/middleware/auth.js +98 -0
  18. package/backend/dist/middleware/auth.js.map +1 -0
  19. package/backend/dist/middleware/index.d.ts +5 -0
  20. package/backend/dist/middleware/index.d.ts.map +1 -0
  21. package/backend/dist/middleware/index.js +62 -0
  22. package/backend/dist/middleware/index.js.map +1 -0
  23. package/backend/dist/models/personaModel.d.ts +37 -0
  24. package/backend/dist/models/personaModel.d.ts.map +1 -0
  25. package/backend/dist/models/personaModel.js +269 -0
  26. package/backend/dist/models/personaModel.js.map +1 -0
  27. package/backend/dist/models/userModel.d.ts +86 -0
  28. package/backend/dist/models/userModel.d.ts.map +1 -0
  29. package/backend/dist/models/userModel.js +212 -0
  30. package/backend/dist/models/userModel.js.map +1 -0
  31. package/backend/dist/routes/auth.d.ts +3 -0
  32. package/backend/dist/routes/auth.d.ts.map +1 -0
  33. package/backend/dist/routes/auth.js +389 -0
  34. package/backend/dist/routes/auth.js.map +1 -0
  35. package/backend/dist/routes/chat.d.ts +3 -0
  36. package/backend/dist/routes/chat.d.ts.map +1 -0
  37. package/backend/dist/routes/chat.js +767 -0
  38. package/backend/dist/routes/chat.js.map +1 -0
  39. package/backend/dist/routes/documents.d.ts +3 -0
  40. package/backend/dist/routes/documents.d.ts.map +1 -0
  41. package/backend/dist/routes/documents.js +244 -0
  42. package/backend/dist/routes/documents.js.map +1 -0
  43. package/backend/dist/routes/ollama.d.ts +3 -0
  44. package/backend/dist/routes/ollama.d.ts.map +1 -0
  45. package/backend/dist/routes/ollama.js +549 -0
  46. package/backend/dist/routes/ollama.js.map +1 -0
  47. package/backend/dist/routes/personas.d.ts +3 -0
  48. package/backend/dist/routes/personas.d.ts.map +1 -0
  49. package/backend/dist/routes/personas.js +505 -0
  50. package/backend/dist/routes/personas.js.map +1 -0
  51. package/backend/dist/routes/plugins.d.ts +3 -0
  52. package/backend/dist/routes/plugins.d.ts.map +1 -0
  53. package/backend/dist/routes/plugins.js +417 -0
  54. package/backend/dist/routes/plugins.js.map +1 -0
  55. package/backend/dist/routes/preferences.d.ts +3 -0
  56. package/backend/dist/routes/preferences.d.ts.map +1 -0
  57. package/backend/dist/routes/preferences.js +303 -0
  58. package/backend/dist/routes/preferences.js.map +1 -0
  59. package/backend/dist/routes/tts.d.ts +3 -0
  60. package/backend/dist/routes/tts.d.ts.map +1 -0
  61. package/backend/dist/routes/tts.js +304 -0
  62. package/backend/dist/routes/tts.js.map +1 -0
  63. package/backend/dist/routes/users.d.ts +3 -0
  64. package/backend/dist/routes/users.d.ts.map +1 -0
  65. package/backend/dist/routes/users.js +246 -0
  66. package/backend/dist/routes/users.js.map +1 -0
  67. package/backend/dist/services/authService.d.ts +51 -0
  68. package/backend/dist/services/authService.d.ts.map +1 -0
  69. package/backend/dist/services/authService.js +153 -0
  70. package/backend/dist/services/authService.js.map +1 -0
  71. package/backend/dist/services/chatService.d.ts +52 -0
  72. package/backend/dist/services/chatService.d.ts.map +1 -0
  73. package/backend/dist/services/chatService.js +645 -0
  74. package/backend/dist/services/chatService.js.map +1 -0
  75. package/backend/dist/services/documentService.d.ts +34 -0
  76. package/backend/dist/services/documentService.d.ts.map +1 -0
  77. package/backend/dist/services/documentService.js +428 -0
  78. package/backend/dist/services/documentService.js.map +1 -0
  79. package/backend/dist/services/encryptionService.d.ts +62 -0
  80. package/backend/dist/services/encryptionService.d.ts.map +1 -0
  81. package/backend/dist/services/encryptionService.js +284 -0
  82. package/backend/dist/services/encryptionService.js.map +1 -0
  83. package/backend/dist/services/memoryService.d.ts +140 -0
  84. package/backend/dist/services/memoryService.d.ts.map +1 -0
  85. package/backend/dist/services/memoryService.js +867 -0
  86. package/backend/dist/services/memoryService.js.map +1 -0
  87. package/backend/dist/services/mutationEngineService.d.ts +49 -0
  88. package/backend/dist/services/mutationEngineService.d.ts.map +1 -0
  89. package/backend/dist/services/mutationEngineService.js +432 -0
  90. package/backend/dist/services/mutationEngineService.js.map +1 -0
  91. package/backend/dist/services/ollamaService.d.ts +55 -0
  92. package/backend/dist/services/ollamaService.d.ts.map +1 -0
  93. package/backend/dist/services/ollamaService.js +450 -0
  94. package/backend/dist/services/ollamaService.js.map +1 -0
  95. package/backend/dist/services/personaService.d.ts +67 -0
  96. package/backend/dist/services/personaService.d.ts.map +1 -0
  97. package/backend/dist/services/personaService.js +373 -0
  98. package/backend/dist/services/personaService.js.map +1 -0
  99. package/backend/dist/services/pluginService.d.ts +42 -0
  100. package/backend/dist/services/pluginService.d.ts.map +1 -0
  101. package/backend/dist/services/pluginService.js +961 -0
  102. package/backend/dist/services/pluginService.js.map +1 -0
  103. package/backend/dist/services/preferencesService.d.ts +35 -0
  104. package/backend/dist/services/preferencesService.d.ts.map +1 -0
  105. package/backend/dist/services/preferencesService.js +255 -0
  106. package/backend/dist/services/preferencesService.js.map +1 -0
  107. package/backend/dist/services/simpleGitHubOAuth.d.ts +48 -0
  108. package/backend/dist/services/simpleGitHubOAuth.d.ts.map +1 -0
  109. package/backend/dist/services/simpleGitHubOAuth.js +203 -0
  110. package/backend/dist/services/simpleGitHubOAuth.js.map +1 -0
  111. package/backend/dist/services/simpleHuggingFaceOAuth.d.ts +43 -0
  112. package/backend/dist/services/simpleHuggingFaceOAuth.d.ts.map +1 -0
  113. package/backend/dist/services/simpleHuggingFaceOAuth.js +159 -0
  114. package/backend/dist/services/simpleHuggingFaceOAuth.js.map +1 -0
  115. package/backend/dist/services/userService.d.ts +1 -0
  116. package/backend/dist/services/userService.d.ts.map +1 -0
  117. package/backend/dist/services/userService.js +18 -0
  118. package/backend/dist/services/userService.js.map +1 -0
  119. package/backend/dist/storage.d.ts +55 -0
  120. package/backend/dist/storage.d.ts.map +1 -0
  121. package/backend/dist/storage.js +741 -0
  122. package/backend/dist/storage.js.map +1 -0
  123. package/backend/dist/test-encryption.d.ts +2 -0
  124. package/backend/dist/test-encryption.d.ts.map +1 -0
  125. package/backend/dist/test-encryption.js +64 -0
  126. package/backend/dist/test-encryption.js.map +1 -0
  127. package/backend/dist/types/index.d.ts +523 -0
  128. package/backend/dist/types/index.d.ts.map +1 -0
  129. package/backend/dist/types/index.js +31 -0
  130. package/backend/dist/types/index.js.map +1 -0
  131. package/backend/dist/utils/generationUtils.d.ts +10 -0
  132. package/backend/dist/utils/generationUtils.d.ts.map +1 -0
  133. package/backend/dist/utils/generationUtils.js +49 -0
  134. package/backend/dist/utils/generationUtils.js.map +1 -0
  135. package/backend/dist/utils/hash.d.ts +29 -0
  136. package/backend/dist/utils/hash.d.ts.map +1 -0
  137. package/backend/dist/utils/hash.js +73 -0
  138. package/backend/dist/utils/hash.js.map +1 -0
  139. package/backend/dist/utils/jwt.d.ts +37 -0
  140. package/backend/dist/utils/jwt.d.ts.map +1 -0
  141. package/backend/dist/utils/jwt.js +86 -0
  142. package/backend/dist/utils/jwt.js.map +1 -0
  143. package/bin/cli.js +150 -0
  144. package/electron/main.js +322 -0
  145. package/frontend/dist/_redirects +1 -0
  146. package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  147. package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  148. package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  149. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  150. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  151. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  152. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  153. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  154. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  155. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  156. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  157. package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  158. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  159. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  160. package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  161. package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  162. package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  163. package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  164. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  165. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  166. package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  167. package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  168. package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  169. package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  170. package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  171. package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  172. package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  173. package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  174. package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  175. package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  176. package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  177. package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  178. package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  179. package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  180. package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  181. package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  182. package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  183. package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  184. package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  185. package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  186. package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  187. package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  188. package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  189. package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  190. package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  191. package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  192. package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  193. package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  194. package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  195. package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  196. package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  197. package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  198. package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  199. package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  200. package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  201. package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  202. package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  203. package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  204. package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  205. package/frontend/dist/assets/index-CRQkB7Wz.js +3 -0
  206. package/frontend/dist/css/index-B1OjddR-.css +1 -0
  207. package/frontend/dist/favicon-dark.png +0 -0
  208. package/frontend/dist/favicon-light.png +0 -0
  209. package/frontend/dist/index.html +23 -0
  210. package/frontend/dist/js/ArtifactContainer-c_oi7XMs.js +23 -0
  211. package/frontend/dist/js/ArtifactDemoPage-CdfwJVXu.js +98 -0
  212. package/frontend/dist/js/ChatPage-CyotkmS0.js +281 -0
  213. package/frontend/dist/js/ModelsPage-DNaziPHc.js +2 -0
  214. package/frontend/dist/js/PersonasPage-DcnbJf8Q.js +13 -0
  215. package/frontend/dist/js/UserManagementPage-DtTf92dS.js +1 -0
  216. package/frontend/dist/js/markdown-vendor-D-79K2xZ.js +22 -0
  217. package/frontend/dist/js/react-vendor-N--QU9DW.js +8 -0
  218. package/frontend/dist/js/router-vendor-B-t91v39.js +3 -0
  219. package/frontend/dist/js/ui-vendor-VxSCY_bv.js +177 -0
  220. package/frontend/dist/js/utils-vendor-DNzxLBGx.js +6 -0
  221. package/frontend/dist/logo-dark.png +0 -0
  222. package/frontend/dist/logo-light.png +0 -0
  223. package/frontend/dist/logo.svg +14 -0
  224. package/package.json +128 -0
  225. package/plugins/anthropic.json +25 -0
  226. package/plugins/elevenlabs.json +58 -0
  227. package/plugins/gemini.json +57 -0
  228. package/plugins/github.json +23 -0
  229. package/plugins/groq.json +25 -0
  230. package/plugins/mistral.json +73 -0
  231. package/plugins/openai-tts.json +38 -0
  232. package/plugins/openai.json +132 -0
  233. package/plugins/openrouter.json +353 -0
@@ -0,0 +1,867 @@
1
+ /*
2
+ * Libre WebUI
3
+ * Copyright (C) 2025 Kroonen AI, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { getDatabaseSafe } from '../db.js';
18
+ import ollamaService from './ollamaService.js';
19
+ import { v4 as uuidv4 } from 'uuid';
20
+ export class MemoryService {
21
+ constructor() {
22
+ this.db = getDatabaseSafe();
23
+ this.embeddingModels = [
24
+ {
25
+ id: 'nomic-embed-text',
26
+ name: 'Nomic Embed Text',
27
+ description: 'High-quality text embeddings from Nomic AI',
28
+ provider: 'ollama',
29
+ dimensions: 768,
30
+ },
31
+ {
32
+ id: 'bge-m3',
33
+ name: 'BGE-M3',
34
+ description: 'Multi-lingual and multi-granularity embedding model',
35
+ provider: 'ollama',
36
+ dimensions: 1024,
37
+ },
38
+ {
39
+ id: 'text-embedding-3-large',
40
+ name: 'OpenAI Text Embedding 3 Large',
41
+ description: "OpenAI's largest text embedding model",
42
+ provider: 'openai',
43
+ dimensions: 3072,
44
+ },
45
+ {
46
+ id: 'text-embedding-3-small',
47
+ name: 'OpenAI Text Embedding 3 Small',
48
+ description: "OpenAI's smaller, faster text embedding model",
49
+ provider: 'openai',
50
+ dimensions: 1536,
51
+ },
52
+ {
53
+ id: 'e5-large-v2',
54
+ name: 'E5 Large v2',
55
+ description: 'Microsoft E5 large text embedding model',
56
+ provider: 'sentence-transformers',
57
+ dimensions: 1024,
58
+ },
59
+ ];
60
+ this.initializeTables();
61
+ }
62
+ /**
63
+ * Ensure database is available
64
+ */
65
+ ensureDatabase() {
66
+ if (!this.db) {
67
+ throw new Error('Database not available');
68
+ }
69
+ return this.db;
70
+ }
71
+ initializeTables() {
72
+ if (!this.db) {
73
+ console.warn('MemoryService: Database not available, skipping table initialization');
74
+ return;
75
+ }
76
+ // Persona memories table - create with basic schema first
77
+ this.db.exec(`
78
+ CREATE TABLE IF NOT EXISTS persona_memories (
79
+ id TEXT PRIMARY KEY,
80
+ user_id TEXT NOT NULL,
81
+ persona_id TEXT NOT NULL,
82
+ content TEXT NOT NULL,
83
+ embedding BLOB, -- Stored as binary for efficiency
84
+ timestamp INTEGER NOT NULL,
85
+ context TEXT,
86
+ importance_score REAL DEFAULT 0.5,
87
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
88
+ FOREIGN KEY (persona_id) REFERENCES personas(id) ON DELETE CASCADE
89
+ )
90
+ `);
91
+ // Create basic indexes first (on columns that always exist)
92
+ this.db.exec(`
93
+ CREATE INDEX IF NOT EXISTS idx_persona_memories_user_persona ON persona_memories(user_id, persona_id);
94
+ CREATE INDEX IF NOT EXISTS idx_persona_memories_timestamp ON persona_memories(timestamp);
95
+ CREATE INDEX IF NOT EXISTS idx_persona_memories_importance ON persona_memories(importance_score);
96
+ `);
97
+ // Add new columns if they don't exist (migration for existing databases)
98
+ // This MUST happen before creating indexes on new columns
99
+ this.migrateDatabase();
100
+ // Now create indexes on the new columns (after migration ensures they exist)
101
+ try {
102
+ this.db.exec(`
103
+ CREATE INDEX IF NOT EXISTS idx_persona_memories_type ON persona_memories(memory_type);
104
+ CREATE INDEX IF NOT EXISTS idx_persona_memories_last_accessed ON persona_memories(last_accessed);
105
+ `);
106
+ }
107
+ catch (error) {
108
+ // Indexes may already exist, ignore
109
+ console.warn('[MemoryService] Index creation:', error);
110
+ }
111
+ }
112
+ /**
113
+ * Migrate database to add new columns for existing tables
114
+ */
115
+ migrateDatabase() {
116
+ if (!this.db)
117
+ return;
118
+ try {
119
+ // Check if new columns exist and add them if not
120
+ const columns = [
121
+ 'memory_type',
122
+ 'access_count',
123
+ 'last_accessed',
124
+ 'decay_factor',
125
+ 'consolidated_from',
126
+ ];
127
+ const defaults = {
128
+ memory_type: "'general'",
129
+ access_count: '0',
130
+ last_accessed: 'NULL',
131
+ decay_factor: '1.0',
132
+ consolidated_from: 'NULL',
133
+ };
134
+ for (const column of columns) {
135
+ try {
136
+ this.db.exec(`ALTER TABLE persona_memories ADD COLUMN ${column} ${this.getColumnType(column)} DEFAULT ${defaults[column]}`);
137
+ console.log(`[MemoryService] Added column ${column} to persona_memories`);
138
+ }
139
+ catch {
140
+ // Column likely already exists, ignore
141
+ }
142
+ }
143
+ }
144
+ catch (error) {
145
+ console.warn('[MemoryService] Migration check:', error);
146
+ }
147
+ }
148
+ /**
149
+ * Get SQLite column type for a given column name
150
+ */
151
+ getColumnType(column) {
152
+ const types = {
153
+ memory_type: 'TEXT',
154
+ access_count: 'INTEGER',
155
+ last_accessed: 'INTEGER',
156
+ decay_factor: 'REAL',
157
+ consolidated_from: 'TEXT',
158
+ };
159
+ return types[column] || 'TEXT';
160
+ }
161
+ /**
162
+ * Classify memory content into a type
163
+ */
164
+ classifyMemoryType(content) {
165
+ const lowerContent = content.toLowerCase();
166
+ // Check for preference indicators
167
+ const preferencePatterns = [
168
+ /i (like|love|prefer|enjoy|hate|dislike|don't like)/i,
169
+ /my favorite/i,
170
+ /i('m| am) (a fan of|into|interested in)/i,
171
+ ];
172
+ if (preferencePatterns.some(p => p.test(lowerContent))) {
173
+ return 'preference';
174
+ }
175
+ // Check for factual information about the user
176
+ const factPatterns = [
177
+ /i (am|'m) (a |an )?(\w+ )?(developer|engineer|designer|student|teacher|doctor|lawyer)/i,
178
+ /i (work|live|study) (at|in|for)/i,
179
+ /my (name|job|profession|age|location|birthday)/i,
180
+ /i have (a |an )?(\d+ )?(kids?|children|dogs?|cats?|pets?)/i,
181
+ ];
182
+ if (factPatterns.some(p => p.test(lowerContent))) {
183
+ return 'fact';
184
+ }
185
+ // Check for emotional content
186
+ const emotionalPatterns = [
187
+ /i('m| am) (feeling|so|really|very) (happy|sad|excited|anxious|worried|stressed|grateful)/i,
188
+ /thank you|thanks|appreciate/i,
189
+ /i('m| am) (sorry|apologize)/i,
190
+ /(love|hate) (this|that|it)/i,
191
+ ];
192
+ if (emotionalPatterns.some(p => p.test(lowerContent))) {
193
+ return 'emotional';
194
+ }
195
+ // Check for instructions
196
+ const instructionPatterns = [
197
+ /please (always|never|remember|don't|do not)/i,
198
+ /i want you to/i,
199
+ /can you (please )?make sure/i,
200
+ /when (i ask|responding|you)/i,
201
+ ];
202
+ if (instructionPatterns.some(p => p.test(lowerContent))) {
203
+ return 'instruction';
204
+ }
205
+ // Check for experiences/stories
206
+ const experiencePatterns = [
207
+ /i (went|did|saw|visited|attended|met|had)/i,
208
+ /yesterday|last (week|month|year)|recently/i,
209
+ /one time|once upon a time|i remember when/i,
210
+ ];
211
+ if (experiencePatterns.some(p => p.test(lowerContent))) {
212
+ return 'experience';
213
+ }
214
+ return 'general';
215
+ }
216
+ /**
217
+ * Calculate enhanced importance score based on multiple factors
218
+ */
219
+ calculateEnhancedImportance(content, memoryType, _context) {
220
+ let score = 0.5; // Base score
221
+ // Adjust based on memory type
222
+ const typeWeights = {
223
+ instruction: 0.9, // Instructions are very important
224
+ fact: 0.8, // Facts about user are important
225
+ preference: 0.75, // Preferences matter
226
+ emotional: 0.7, // Emotional moments are memorable
227
+ experience: 0.6, // Experiences are contextual
228
+ context: 0.4, // Context is temporary
229
+ general: 0.5, // Default
230
+ };
231
+ score = typeWeights[memoryType];
232
+ // Adjust based on content length (longer = potentially more detailed/important)
233
+ const wordCount = content.split(/\s+/).length;
234
+ if (wordCount > 50)
235
+ score = Math.min(1.0, score + 0.1);
236
+ else if (wordCount < 10)
237
+ score = Math.max(0.1, score - 0.1);
238
+ // Adjust based on specificity (names, numbers, dates)
239
+ const specificityIndicators = [
240
+ /\b\d{4}\b/, // Years
241
+ /\b\d{1,2}\/\d{1,2}\b/, // Dates
242
+ /\b[A-Z][a-z]+\b/, // Proper nouns
243
+ /\b\d+\s*(years?|months?|days?|hours?)\b/i, // Time durations
244
+ ];
245
+ const specificityCount = specificityIndicators.filter(p => p.test(content)).length;
246
+ score = Math.min(1.0, score + specificityCount * 0.05);
247
+ // Adjust based on question presence (questions often indicate important topics)
248
+ if (content.includes('?')) {
249
+ score = Math.min(1.0, score + 0.05);
250
+ }
251
+ // Clamp between 0.1 and 1.0
252
+ return Math.max(0.1, Math.min(1.0, score));
253
+ }
254
+ /**
255
+ * Apply time-based decay to importance score
256
+ */
257
+ applyDecay(originalImportance, timestamp, accessCount = 0, lastAccessed) {
258
+ const now = Date.now();
259
+ const ageInDays = (now - timestamp) / (1000 * 60 * 60 * 24);
260
+ const timeSinceAccess = lastAccessed
261
+ ? (now - lastAccessed) / (1000 * 60 * 60 * 24)
262
+ : ageInDays;
263
+ // Base decay: memories lose ~10% importance per month if not accessed
264
+ const decayRate = 0.003; // ~10% per month
265
+ let decayedImportance = originalImportance * Math.exp(-decayRate * timeSinceAccess);
266
+ // Boost for frequently accessed memories (reinforcement)
267
+ const accessBoost = Math.min(0.3, accessCount * 0.02);
268
+ decayedImportance = Math.min(1.0, decayedImportance + accessBoost);
269
+ // Never let importance drop below 0.1 for recent memories (< 7 days)
270
+ if (ageInDays < 7) {
271
+ decayedImportance = Math.max(0.3, decayedImportance);
272
+ }
273
+ return Math.max(0.1, Math.min(1.0, decayedImportance));
274
+ }
275
+ /**
276
+ * Get available embedding models
277
+ */
278
+ getEmbeddingModels() {
279
+ return this.embeddingModels;
280
+ }
281
+ /**
282
+ * Generate embedding for text using specified model
283
+ */
284
+ async generateEmbedding(text, model) {
285
+ try {
286
+ // For now, use Ollama for embedding generation
287
+ // TODO: Add support for other providers (OpenAI, Sentence Transformers, etc.)
288
+ const response = await ollamaService.generateEmbeddings({
289
+ model,
290
+ input: text,
291
+ });
292
+ return response.embeddings[0] || null;
293
+ }
294
+ catch (error) {
295
+ console.error('Failed to generate embedding:', error);
296
+ return null;
297
+ }
298
+ }
299
+ /**
300
+ * Store a memory entry with embedding and automatic classification
301
+ */
302
+ async storeMemory(userId, personaId, content, embeddingModel, context, importanceScore, memoryType) {
303
+ const id = uuidv4();
304
+ const timestamp = Date.now();
305
+ // Auto-classify memory type if not provided
306
+ const classifiedType = memoryType || this.classifyMemoryType(content);
307
+ // Calculate enhanced importance if not provided
308
+ const calculatedImportance = importanceScore !== undefined
309
+ ? importanceScore
310
+ : this.calculateEnhancedImportance(content, classifiedType, context);
311
+ // Check for similar existing memories before storing
312
+ const existingSimilar = await this.findSimilarMemories(userId, personaId, content, embeddingModel, 0.85 // High similarity threshold for deduplication
313
+ );
314
+ // If very similar memory exists, reinforce it instead of creating new
315
+ if (existingSimilar.length > 0) {
316
+ const mostSimilar = existingSimilar[0];
317
+ console.log(`[MEMORY] Found similar memory (${(mostSimilar.similarity_score * 100).toFixed(1)}% similar), reinforcing instead of creating new`);
318
+ await this.reinforceMemory(mostSimilar.entry.id);
319
+ return mostSimilar.entry;
320
+ }
321
+ // Generate embedding
322
+ const embedding = await this.generateEmbedding(content, embeddingModel);
323
+ const db = this.ensureDatabase();
324
+ const stmt = db.prepare(`
325
+ INSERT INTO persona_memories (
326
+ id, user_id, persona_id, content, embedding, timestamp, context, importance_score,
327
+ memory_type, access_count, last_accessed, decay_factor
328
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
329
+ `);
330
+ stmt.run(id, userId, personaId, content, embedding ? Buffer.from(new Float32Array(embedding).buffer) : null, timestamp, context || null, calculatedImportance, classifiedType, 0, // access_count
331
+ null, // last_accessed
332
+ 1.0 // decay_factor
333
+ );
334
+ console.log(`[MEMORY] Stored: type=${classifiedType}, importance=${calculatedImportance.toFixed(2)}, id=${id}, content="${content.substring(0, 50)}..."`);
335
+ return {
336
+ id,
337
+ user_id: userId,
338
+ persona_id: personaId,
339
+ content,
340
+ embedding: embedding || undefined,
341
+ timestamp,
342
+ context,
343
+ importance_score: calculatedImportance,
344
+ memory_type: classifiedType,
345
+ access_count: 0,
346
+ decay_factor: 1.0,
347
+ };
348
+ }
349
+ /**
350
+ * Find similar memories (for deduplication)
351
+ */
352
+ async findSimilarMemories(userId, personaId, content, embeddingModel, minSimilarity) {
353
+ const queryEmbedding = await this.generateEmbedding(content, embeddingModel);
354
+ if (!queryEmbedding)
355
+ return [];
356
+ const db = this.ensureDatabase();
357
+ const stmt = db.prepare(`
358
+ SELECT id, user_id, persona_id, content, embedding, timestamp, context, importance_score,
359
+ memory_type, access_count, last_accessed, decay_factor
360
+ FROM persona_memories
361
+ WHERE user_id = ? AND persona_id = ? AND embedding IS NOT NULL
362
+ ORDER BY timestamp DESC
363
+ LIMIT 50
364
+ `);
365
+ const memories = stmt.all(userId, personaId);
366
+ const results = [];
367
+ for (const memory of memories) {
368
+ const embeddingArray = Array.from(new Float32Array(memory.embedding.buffer));
369
+ const similarity = this.cosineSimilarity(queryEmbedding, embeddingArray);
370
+ if (similarity >= minSimilarity) {
371
+ results.push({
372
+ entry: {
373
+ id: memory.id,
374
+ user_id: memory.user_id,
375
+ persona_id: memory.persona_id,
376
+ content: memory.content,
377
+ embedding: embeddingArray,
378
+ timestamp: memory.timestamp,
379
+ context: memory.context || undefined,
380
+ importance_score: memory.importance_score,
381
+ },
382
+ similarity_score: similarity,
383
+ relevance_rank: 0,
384
+ });
385
+ }
386
+ }
387
+ return results.sort((a, b) => b.similarity_score - a.similarity_score);
388
+ }
389
+ /**
390
+ * Reinforce a memory (increases importance and access count)
391
+ */
392
+ async reinforceMemory(memoryId) {
393
+ const db = this.ensureDatabase();
394
+ const stmt = db.prepare(`
395
+ UPDATE persona_memories
396
+ SET access_count = COALESCE(access_count, 0) + 1,
397
+ last_accessed = ?,
398
+ importance_score = MIN(1.0, COALESCE(importance_score, 0.5) + 0.05)
399
+ WHERE id = ?
400
+ `);
401
+ const result = stmt.run(Date.now(), memoryId);
402
+ return result.changes > 0;
403
+ }
404
+ /**
405
+ * Calculate cosine similarity between two vectors
406
+ */
407
+ cosineSimilarity(a, b) {
408
+ if (a.length !== b.length)
409
+ return 0;
410
+ let dotProduct = 0;
411
+ let normA = 0;
412
+ let normB = 0;
413
+ for (let i = 0; i < a.length; i++) {
414
+ dotProduct += a[i] * b[i];
415
+ normA += a[i] * a[i];
416
+ normB += b[i] * b[i];
417
+ }
418
+ if (normA === 0 || normB === 0)
419
+ return 0;
420
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
421
+ }
422
+ /**
423
+ * Search memories using semantic similarity with enhanced relevance scoring
424
+ */
425
+ async searchMemories(userId, personaId, query, embeddingModel, topK = 5, minSimilarity = 0.3, memoryTypes // Optional filter by memory types
426
+ ) {
427
+ // Generate embedding for query
428
+ const queryEmbedding = await this.generateEmbedding(query, embeddingModel);
429
+ if (!queryEmbedding) {
430
+ return [];
431
+ }
432
+ // Get all memories for this user/persona with embeddings
433
+ const db = this.ensureDatabase();
434
+ let sql = `
435
+ SELECT id, user_id, persona_id, content, embedding, timestamp, context, importance_score,
436
+ memory_type, access_count, last_accessed, decay_factor
437
+ FROM persona_memories
438
+ WHERE user_id = ? AND persona_id = ? AND embedding IS NOT NULL
439
+ `;
440
+ // Add memory type filter if specified
441
+ if (memoryTypes && memoryTypes.length > 0) {
442
+ sql += ` AND memory_type IN (${memoryTypes.map(() => '?').join(',')})`;
443
+ }
444
+ sql += ` ORDER BY timestamp DESC`;
445
+ const stmt = db.prepare(sql);
446
+ const params = memoryTypes
447
+ ? [userId, personaId, ...memoryTypes]
448
+ : [userId, personaId];
449
+ const memories = stmt.all(...params);
450
+ // Calculate enhanced relevance scores
451
+ const results = [];
452
+ for (const memory of memories) {
453
+ // Convert buffer back to float array
454
+ const embeddingArray = Array.from(new Float32Array(memory.embedding.buffer));
455
+ const similarity = this.cosineSimilarity(queryEmbedding, embeddingArray);
456
+ if (similarity >= minSimilarity) {
457
+ // Calculate decayed importance
458
+ const decayedImportance = this.applyDecay(memory.importance_score, memory.timestamp, memory.access_count || 0, memory.last_accessed || undefined);
459
+ results.push({
460
+ entry: {
461
+ id: memory.id,
462
+ user_id: memory.user_id,
463
+ persona_id: memory.persona_id,
464
+ content: memory.content,
465
+ embedding: embeddingArray,
466
+ timestamp: memory.timestamp,
467
+ context: memory.context || undefined,
468
+ importance_score: decayedImportance,
469
+ },
470
+ similarity_score: similarity,
471
+ relevance_rank: 0, // Will be set after sorting
472
+ });
473
+ }
474
+ }
475
+ // Sort by composite relevance score
476
+ results.sort((a, b) => {
477
+ const now = Date.now();
478
+ const ageA = (now - a.entry.timestamp) / (1000 * 60 * 60);
479
+ const ageB = (now - b.entry.timestamp) / (1000 * 60 * 60);
480
+ const recencyA = ageA < 24 ? 0.1 : ageA < 168 ? 0.05 : 0;
481
+ const recencyB = ageB < 24 ? 0.1 : ageB < 168 ? 0.05 : 0;
482
+ const scoreA = a.similarity_score * 0.5 +
483
+ (a.entry.importance_score || 0.5) * 0.25 +
484
+ recencyA;
485
+ const scoreB = b.similarity_score * 0.5 +
486
+ (b.entry.importance_score || 0.5) * 0.25 +
487
+ recencyB;
488
+ return scoreB - scoreA;
489
+ });
490
+ // Update access count for returned memories
491
+ const topResults = results.slice(0, topK);
492
+ for (const result of topResults) {
493
+ await this.updateMemoryAccess(result.entry.id);
494
+ }
495
+ // Set relevance ranks and return top-k
496
+ return topResults.map((result, index) => ({
497
+ ...result,
498
+ relevance_rank: index + 1,
499
+ }));
500
+ }
501
+ /**
502
+ * Update memory access tracking
503
+ */
504
+ async updateMemoryAccess(memoryId) {
505
+ try {
506
+ const db = this.ensureDatabase();
507
+ const stmt = db.prepare(`
508
+ UPDATE persona_memories
509
+ SET access_count = COALESCE(access_count, 0) + 1,
510
+ last_accessed = ?
511
+ WHERE id = ?
512
+ `);
513
+ stmt.run(Date.now(), memoryId);
514
+ }
515
+ catch (error) {
516
+ console.warn('[MemoryService] Failed to update memory access:', error);
517
+ }
518
+ }
519
+ /**
520
+ * Get all memories for a persona
521
+ */
522
+ async getMemories(userId, personaId, limit = 100, offset = 0) {
523
+ const db = this.ensureDatabase();
524
+ const stmt = db.prepare(`
525
+ SELECT id, user_id, persona_id, content, embedding, timestamp, context, importance_score
526
+ FROM persona_memories
527
+ WHERE user_id = ? AND persona_id = ?
528
+ ORDER BY timestamp DESC
529
+ LIMIT ? OFFSET ?
530
+ `);
531
+ const memories = stmt.all(userId, personaId, limit, offset);
532
+ return memories.map(memory => ({
533
+ id: memory.id,
534
+ user_id: memory.user_id,
535
+ persona_id: memory.persona_id,
536
+ content: memory.content,
537
+ embedding: memory.embedding
538
+ ? Array.from(new Float32Array(memory.embedding.buffer))
539
+ : undefined,
540
+ timestamp: memory.timestamp,
541
+ context: memory.context || undefined,
542
+ importance_score: memory.importance_score,
543
+ }));
544
+ }
545
+ /**
546
+ * Get memory count for a persona
547
+ */
548
+ async getMemoryCount(userId, personaId) {
549
+ console.log(`[MEMORY-DEBUG] getMemoryCount called - userId: ${userId}, personaId: ${personaId}`);
550
+ const db = this.ensureDatabase();
551
+ const stmt = db.prepare(`
552
+ SELECT COUNT(*) as count
553
+ FROM persona_memories
554
+ WHERE user_id = ? AND persona_id = ?
555
+ `);
556
+ const result = stmt.get(userId, personaId);
557
+ console.log(`[MEMORY-DEBUG] Query result:`, result);
558
+ console.log(`[MEMORY-DEBUG] Memory count: ${result.count}`);
559
+ return result.count;
560
+ }
561
+ /**
562
+ * Get memory status for a persona
563
+ */
564
+ async getMemoryStatus(userId, personaId) {
565
+ try {
566
+ const memoryCount = await this.getMemoryCount(userId, personaId);
567
+ // Calculate approximate memory size (rough estimate)
568
+ const avgMemorySize = 1024; // Average bytes per memory entry
569
+ const sizeMb = (memoryCount * avgMemorySize) / (1024 * 1024);
570
+ return {
571
+ memory_count: memoryCount,
572
+ last_backup: undefined, // Could be implemented later
573
+ size_mb: Math.round(sizeMb * 100) / 100, // Round to 2 decimal places
574
+ };
575
+ }
576
+ catch (error) {
577
+ console.error('Error getting memory status:', error);
578
+ throw new Error('Failed to get memory status');
579
+ }
580
+ }
581
+ /**
582
+ * Delete all memories for a persona (wipe)
583
+ */
584
+ async wipeMemories(userId, personaId) {
585
+ const db = this.ensureDatabase();
586
+ const stmt = db.prepare(`
587
+ DELETE FROM persona_memories
588
+ WHERE user_id = ? AND persona_id = ?
589
+ `);
590
+ const result = stmt.run(userId, personaId);
591
+ return result.changes;
592
+ }
593
+ /**
594
+ * Export memories for backup
595
+ */
596
+ async exportMemories(userId, personaId) {
597
+ return this.getMemories(userId, personaId, 10000); // Export all
598
+ }
599
+ /**
600
+ * Import memories from backup
601
+ */
602
+ async importMemories(memories, targetUserId) {
603
+ let imported = 0;
604
+ const db = this.ensureDatabase();
605
+ const stmt = db.prepare(`
606
+ INSERT INTO persona_memories (
607
+ id, user_id, persona_id, content, embedding, timestamp, context, importance_score
608
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
609
+ `);
610
+ for (const memory of memories) {
611
+ try {
612
+ stmt.run(memory.id, targetUserId, // Use target user ID
613
+ memory.persona_id, memory.content, memory.embedding
614
+ ? Buffer.from(new Float32Array(memory.embedding).buffer)
615
+ : null, memory.timestamp, memory.context || null, memory.importance_score || 0.5);
616
+ imported++;
617
+ }
618
+ catch (error) {
619
+ console.error('Failed to import memory:', error);
620
+ // Continue with next memory
621
+ }
622
+ }
623
+ return imported;
624
+ }
625
+ /**
626
+ * Update memory importance score
627
+ */
628
+ async updateMemoryImportance(memoryId, importanceScore) {
629
+ const db = this.ensureDatabase();
630
+ const stmt = db.prepare(`
631
+ UPDATE persona_memories
632
+ SET importance_score = ?
633
+ WHERE id = ?
634
+ `);
635
+ const result = stmt.run(importanceScore, memoryId);
636
+ return result.changes > 0;
637
+ }
638
+ /**
639
+ * Delete old memories based on retention policy
640
+ */
641
+ async cleanupOldMemories(userId, personaId, retentionDays) {
642
+ const cutoffTimestamp = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
643
+ const db = this.ensureDatabase();
644
+ const stmt = db.prepare(`
645
+ DELETE FROM persona_memories
646
+ WHERE user_id = ? AND persona_id = ? AND timestamp < ?
647
+ AND importance_score < 0.7
648
+ `);
649
+ const result = stmt.run(userId, personaId, cutoffTimestamp);
650
+ return result.changes;
651
+ }
652
+ /**
653
+ * Consolidate similar memories to reduce redundancy and save space
654
+ * This merges memories with high similarity into a single, more comprehensive memory
655
+ */
656
+ async consolidateMemories(userId, personaId, embeddingModel, similarityThreshold = 0.8) {
657
+ const db = this.ensureDatabase();
658
+ let consolidated = 0;
659
+ let deleted = 0;
660
+ try {
661
+ // Get all memories with embeddings
662
+ const stmt = db.prepare(`
663
+ SELECT id, content, embedding, timestamp, importance_score, memory_type, access_count
664
+ FROM persona_memories
665
+ WHERE user_id = ? AND persona_id = ? AND embedding IS NOT NULL
666
+ ORDER BY importance_score DESC, timestamp DESC
667
+ `);
668
+ const memories = stmt.all(userId, personaId);
669
+ const processedIds = new Set();
670
+ const toDelete = [];
671
+ for (let i = 0; i < memories.length; i++) {
672
+ const memory = memories[i];
673
+ if (processedIds.has(memory.id))
674
+ continue;
675
+ const embeddingA = Array.from(new Float32Array(memory.embedding.buffer));
676
+ const similarMemories = [];
677
+ // Find similar memories
678
+ for (let j = i + 1; j < memories.length; j++) {
679
+ const other = memories[j];
680
+ if (processedIds.has(other.id))
681
+ continue;
682
+ const embeddingB = Array.from(new Float32Array(other.embedding.buffer));
683
+ const similarity = this.cosineSimilarity(embeddingA, embeddingB);
684
+ if (similarity >= similarityThreshold) {
685
+ similarMemories.push(other);
686
+ processedIds.add(other.id);
687
+ }
688
+ }
689
+ // If we found similar memories, consolidate them
690
+ if (similarMemories.length > 0) {
691
+ processedIds.add(memory.id);
692
+ // Create consolidated content
693
+ const allContent = [
694
+ memory.content,
695
+ ...similarMemories.map(m => m.content),
696
+ ];
697
+ const consolidatedContent = this.createConsolidatedContent(allContent);
698
+ // Calculate combined importance (weighted average with boost)
699
+ const allImportances = [
700
+ memory.importance_score,
701
+ ...similarMemories.map(m => m.importance_score),
702
+ ];
703
+ const avgImportance = allImportances.reduce((a, b) => a + b, 0) / allImportances.length;
704
+ const consolidatedImportance = Math.min(1.0, avgImportance * 1.1); // 10% boost for consolidation
705
+ // Use the most common memory type
706
+ const types = [
707
+ memory.memory_type,
708
+ ...similarMemories.map(m => m.memory_type),
709
+ ].filter(Boolean);
710
+ const typeCount = {};
711
+ types.forEach(t => {
712
+ if (t)
713
+ typeCount[t] = (typeCount[t] || 0) + 1;
714
+ });
715
+ const consolidatedType = Object.entries(typeCount).sort((a, b) => b[1] - a[1])[0]?.[0] ||
716
+ 'general';
717
+ // Generate new embedding for consolidated content
718
+ const newEmbedding = await this.generateEmbedding(consolidatedContent, embeddingModel);
719
+ // Store consolidated memory
720
+ const consolidatedFromIds = [
721
+ memory.id,
722
+ ...similarMemories.map(m => m.id),
723
+ ];
724
+ const insertStmt = db.prepare(`
725
+ INSERT INTO persona_memories (
726
+ id, user_id, persona_id, content, embedding, timestamp, context, importance_score,
727
+ memory_type, access_count, last_accessed, decay_factor, consolidated_from
728
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
729
+ `);
730
+ insertStmt.run(uuidv4(), userId, personaId, consolidatedContent, newEmbedding
731
+ ? Buffer.from(new Float32Array(newEmbedding).buffer)
732
+ : null, Date.now(), `Consolidated from ${consolidatedFromIds.length} memories`, consolidatedImportance, consolidatedType, similarMemories.reduce((sum, m) => sum + (m.access_count || 0), memory.access_count || 0), Date.now(), 1.0, JSON.stringify(consolidatedFromIds));
733
+ // Mark original memories for deletion
734
+ toDelete.push(...consolidatedFromIds);
735
+ consolidated++;
736
+ }
737
+ }
738
+ // Delete original memories that were consolidated
739
+ if (toDelete.length > 0) {
740
+ const deleteStmt = db.prepare(`
741
+ DELETE FROM persona_memories WHERE id IN (${toDelete.map(() => '?').join(',')})
742
+ `);
743
+ deleteStmt.run(...toDelete);
744
+ deleted = toDelete.length;
745
+ }
746
+ console.log(`[MEMORY] Consolidation complete: ${consolidated} groups merged, ${deleted} memories deleted`);
747
+ }
748
+ catch (error) {
749
+ console.error('[MEMORY] Consolidation error:', error);
750
+ }
751
+ return { consolidated, deleted };
752
+ }
753
+ /**
754
+ * Create consolidated content from multiple similar memories
755
+ */
756
+ createConsolidatedContent(contents) {
757
+ if (contents.length === 1)
758
+ return contents[0];
759
+ // For now, use the longest content as base and note the count
760
+ const sorted = [...contents].sort((a, b) => b.length - a.length);
761
+ const base = sorted[0];
762
+ // Add a note about consolidation
763
+ if (contents.length === 2) {
764
+ return base;
765
+ }
766
+ return `${base} (consolidated from ${contents.length} related interactions)`;
767
+ }
768
+ /**
769
+ * Get memory statistics for a persona
770
+ */
771
+ async getMemoryStats(userId, personaId) {
772
+ const db = this.ensureDatabase();
773
+ // Get counts by type
774
+ const typeStmt = db.prepare(`
775
+ SELECT memory_type, COUNT(*) as count
776
+ FROM persona_memories
777
+ WHERE user_id = ? AND persona_id = ?
778
+ GROUP BY memory_type
779
+ `);
780
+ const typeCounts = typeStmt.all(userId, personaId);
781
+ // Get aggregate stats
782
+ const statsStmt = db.prepare(`
783
+ SELECT
784
+ COUNT(*) as total_count,
785
+ AVG(importance_score) as avg_importance,
786
+ MIN(timestamp) as oldest_memory,
787
+ MAX(timestamp) as newest_memory,
788
+ SUM(COALESCE(access_count, 0)) as total_accesses
789
+ FROM persona_memories
790
+ WHERE user_id = ? AND persona_id = ?
791
+ `);
792
+ const stats = statsStmt.get(userId, personaId);
793
+ const byType = {};
794
+ typeCounts.forEach(({ memory_type, count }) => {
795
+ byType[memory_type || 'general'] = count;
796
+ });
797
+ return {
798
+ total_count: stats.total_count,
799
+ by_type: byType,
800
+ avg_importance: stats.avg_importance || 0.5,
801
+ oldest_memory: stats.oldest_memory,
802
+ newest_memory: stats.newest_memory,
803
+ total_accesses: stats.total_accesses || 0,
804
+ };
805
+ }
806
+ /**
807
+ * Apply decay to all memories (should be called periodically)
808
+ */
809
+ async applyGlobalDecay(userId, personaId) {
810
+ const db = this.ensureDatabase();
811
+ // Get all memories
812
+ const selectStmt = db.prepare(`
813
+ SELECT id, importance_score, timestamp, access_count, last_accessed
814
+ FROM persona_memories
815
+ WHERE user_id = ? AND persona_id = ?
816
+ `);
817
+ const memories = selectStmt.all(userId, personaId);
818
+ let updated = 0;
819
+ const updateStmt = db.prepare(`
820
+ UPDATE persona_memories
821
+ SET importance_score = ?, decay_factor = ?
822
+ WHERE id = ?
823
+ `);
824
+ for (const memory of memories) {
825
+ const newImportance = this.applyDecay(memory.importance_score, memory.timestamp, memory.access_count || 0, memory.last_accessed || undefined);
826
+ // Only update if importance changed significantly
827
+ if (Math.abs(newImportance - memory.importance_score) > 0.01) {
828
+ const decayFactor = newImportance / memory.importance_score;
829
+ updateStmt.run(newImportance, decayFactor, memory.id);
830
+ updated++;
831
+ }
832
+ }
833
+ console.log(`[MEMORY] Applied decay to ${updated} memories`);
834
+ return updated;
835
+ }
836
+ /**
837
+ * Get important memories (facts, preferences, instructions) that should always be included
838
+ */
839
+ async getCoreMemories(userId, personaId, limit = 5) {
840
+ const db = this.ensureDatabase();
841
+ const stmt = db.prepare(`
842
+ SELECT id, user_id, persona_id, content, embedding, timestamp, context, importance_score,
843
+ memory_type, access_count, last_accessed
844
+ FROM persona_memories
845
+ WHERE user_id = ? AND persona_id = ?
846
+ AND memory_type IN ('fact', 'preference', 'instruction')
847
+ AND importance_score >= 0.7
848
+ ORDER BY importance_score DESC, access_count DESC
849
+ LIMIT ?
850
+ `);
851
+ const memories = stmt.all(userId, personaId, limit);
852
+ return memories.map(memory => ({
853
+ id: memory.id,
854
+ user_id: memory.user_id,
855
+ persona_id: memory.persona_id,
856
+ content: memory.content,
857
+ embedding: memory.embedding
858
+ ? Array.from(new Float32Array(memory.embedding.buffer))
859
+ : undefined,
860
+ timestamp: memory.timestamp,
861
+ context: memory.context || undefined,
862
+ importance_score: memory.importance_score,
863
+ }));
864
+ }
865
+ }
866
+ export const memoryService = new MemoryService();
867
+ //# sourceMappingURL=memoryService.js.map