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,741 @@
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 fs from 'fs';
18
+ import path from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import { v4 as uuidv4 } from 'uuid';
21
+ import bcrypt from 'bcrypt';
22
+ import getDatabase, { isDatabaseInitialized } from './db.js';
23
+ import { encryptionService } from './services/encryptionService.js';
24
+ // Get __dirname equivalent for ES modules
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+ class StorageService {
28
+ constructor() {
29
+ this.useSQLite = false;
30
+ this.sessionsFile = path.join(__dirname, '..', 'sessions.json');
31
+ this.preferencesFile = path.join(__dirname, '..', 'preferences.json');
32
+ this.documentsFile = path.join(__dirname, '..', 'documents.json');
33
+ this.documentChunksFile = path.join(__dirname, '..', 'document-chunks.json');
34
+ // Check if SQLite should be used
35
+ this.useSQLite = isDatabaseInitialized();
36
+ console.log(`Storage mode: ${this.useSQLite ? 'SQLite' : 'JSON'}`);
37
+ }
38
+ // =================================
39
+ // USER MANAGEMENT
40
+ // =================================
41
+ async createUser(username, email, password, role = 'user') {
42
+ const userId = uuidv4();
43
+ const passwordHash = await bcrypt.hash(password, 12);
44
+ const now = Date.now();
45
+ const user = {
46
+ id: userId,
47
+ username,
48
+ email,
49
+ password_hash: passwordHash,
50
+ role,
51
+ created_at: now,
52
+ updated_at: now,
53
+ };
54
+ if (this.useSQLite) {
55
+ const db = getDatabase();
56
+ const stmt = db.prepare(`
57
+ INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at)
58
+ VALUES (?, ?, ?, ?, ?, ?, ?)
59
+ `);
60
+ // Encrypt sensitive user data
61
+ const encryptedEmail = user.email
62
+ ? encryptionService.encrypt(user.email)
63
+ : null;
64
+ stmt.run(user.id, user.username, encryptedEmail, user.password_hash, user.role, user.created_at, user.updated_at);
65
+ }
66
+ return user;
67
+ }
68
+ getUser(userId) {
69
+ if (this.useSQLite) {
70
+ const db = getDatabase();
71
+ const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
72
+ const user = stmt.get(userId);
73
+ if (user && user.email) {
74
+ // Decrypt email
75
+ user.email = encryptionService.decrypt(user.email);
76
+ }
77
+ return user;
78
+ }
79
+ return undefined;
80
+ }
81
+ getUserByUsername(username) {
82
+ if (this.useSQLite) {
83
+ const db = getDatabase();
84
+ const stmt = db.prepare('SELECT * FROM users WHERE username = ?');
85
+ const user = stmt.get(username);
86
+ if (user && user.email) {
87
+ // Decrypt email
88
+ user.email = encryptionService.decrypt(user.email);
89
+ }
90
+ return user;
91
+ }
92
+ return undefined;
93
+ }
94
+ // =================================
95
+ // SESSION MANAGEMENT
96
+ // =================================
97
+ getAllSessions(userId = 'default') {
98
+ if (this.useSQLite) {
99
+ const db = getDatabase();
100
+ // Get sessions
101
+ const sessionsStmt = db.prepare(`
102
+ SELECT * FROM sessions WHERE user_id = ? ORDER BY updated_at DESC
103
+ `);
104
+ const sessions = sessionsStmt.all(userId);
105
+ // Get ALL messages for each session (including branches for side-by-side display)
106
+ const messagesStmt = db.prepare(`
107
+ SELECT * FROM session_messages
108
+ WHERE session_id = ?
109
+ ORDER BY message_index ASC, branch_index ASC
110
+ `);
111
+ // Get sibling counts for branching (count all variants for each parent)
112
+ const siblingCountStmt = db.prepare(`
113
+ SELECT parent_id, COUNT(*) as count FROM session_messages
114
+ WHERE session_id = ? AND parent_id IS NOT NULL
115
+ GROUP BY parent_id
116
+ `);
117
+ return sessions.map(session => {
118
+ const messages = messagesStmt.all(session.id);
119
+ const siblingCounts = siblingCountStmt.all(session.id);
120
+ // Create a map for quick lookup of sibling counts
121
+ const siblingCountMap = new Map();
122
+ for (const sc of siblingCounts) {
123
+ // Add 1 to include the original message in the count
124
+ siblingCountMap.set(sc.parent_id, sc.count + 1);
125
+ }
126
+ // Decrypt session data
127
+ const decryptedTitle = encryptionService.decrypt(session.title);
128
+ return {
129
+ id: session.id,
130
+ title: decryptedTitle,
131
+ model: session.model,
132
+ personaId: session.persona_id || undefined,
133
+ createdAt: session.created_at,
134
+ updatedAt: session.updated_at,
135
+ messages: messages.map(msg => {
136
+ // Decrypt message data
137
+ const decryptedContent = encryptionService.decrypt(msg.content);
138
+ const decryptedImages = msg.images
139
+ ? JSON.parse(encryptionService.decrypt(msg.images))
140
+ : undefined;
141
+ const decryptedStatistics = msg.statistics
142
+ ? JSON.parse(encryptionService.decrypt(msg.statistics))
143
+ : undefined;
144
+ const decryptedArtifacts = msg.artifacts
145
+ ? JSON.parse(encryptionService.decrypt(msg.artifacts))
146
+ : undefined;
147
+ // Calculate sibling count: if this message has variants, count them
148
+ // A message has siblings if it's a parent (has variants) or is a variant itself
149
+ const parentId = msg.parent_id || msg.id;
150
+ const siblingCount = siblingCountMap.get(parentId) || 1;
151
+ return {
152
+ id: msg.id,
153
+ role: msg.role,
154
+ content: decryptedContent,
155
+ timestamp: msg.timestamp,
156
+ model: msg.model,
157
+ images: decryptedImages,
158
+ statistics: decryptedStatistics,
159
+ artifacts: decryptedArtifacts,
160
+ parentId: msg.parent_id,
161
+ branchIndex: msg.branch_index ?? 0,
162
+ isActive: msg.is_active !== 0,
163
+ siblingCount: siblingCount > 1 ? siblingCount : undefined,
164
+ };
165
+ }),
166
+ };
167
+ });
168
+ }
169
+ else {
170
+ // Fallback to JSON
171
+ try {
172
+ if (fs.existsSync(this.sessionsFile)) {
173
+ const data = fs.readFileSync(this.sessionsFile, 'utf8');
174
+ return JSON.parse(data);
175
+ }
176
+ }
177
+ catch (error) {
178
+ console.error('Failed to load sessions from JSON:', error);
179
+ }
180
+ }
181
+ return [];
182
+ }
183
+ getSession(sessionId, userId = 'default') {
184
+ if (this.useSQLite) {
185
+ const db = getDatabase();
186
+ // Get session
187
+ const sessionStmt = db.prepare(`
188
+ SELECT * FROM sessions WHERE id = ? AND user_id = ?
189
+ `);
190
+ const session = sessionStmt.get(sessionId, userId);
191
+ if (!session)
192
+ return undefined;
193
+ // Get ALL messages (including branches for side-by-side display)
194
+ const messagesStmt = db.prepare(`
195
+ SELECT * FROM session_messages
196
+ WHERE session_id = ?
197
+ ORDER BY message_index ASC, branch_index ASC
198
+ `);
199
+ const messages = messagesStmt.all(sessionId);
200
+ // Get sibling counts for branching
201
+ const siblingCountStmt = db.prepare(`
202
+ SELECT parent_id, COUNT(*) as count FROM session_messages
203
+ WHERE session_id = ? AND parent_id IS NOT NULL
204
+ GROUP BY parent_id
205
+ `);
206
+ const siblingCounts = siblingCountStmt.all(sessionId);
207
+ // Create a map for quick lookup of sibling counts
208
+ const siblingCountMap = new Map();
209
+ for (const sc of siblingCounts) {
210
+ siblingCountMap.set(sc.parent_id, sc.count + 1);
211
+ }
212
+ // Decrypt session data
213
+ const decryptedTitle = encryptionService.decrypt(session.title);
214
+ return {
215
+ id: session.id,
216
+ title: decryptedTitle,
217
+ model: session.model,
218
+ createdAt: session.created_at,
219
+ updatedAt: session.updated_at,
220
+ messages: messages.map(msg => {
221
+ // Decrypt message data
222
+ const decryptedContent = encryptionService.decrypt(msg.content);
223
+ const decryptedImages = msg.images
224
+ ? JSON.parse(encryptionService.decrypt(msg.images))
225
+ : undefined;
226
+ const decryptedStatistics = msg.statistics
227
+ ? JSON.parse(encryptionService.decrypt(msg.statistics))
228
+ : undefined;
229
+ const decryptedArtifacts = msg.artifacts
230
+ ? JSON.parse(encryptionService.decrypt(msg.artifacts))
231
+ : undefined;
232
+ const parentId = msg.parent_id || msg.id;
233
+ const siblingCount = siblingCountMap.get(parentId) || 1;
234
+ return {
235
+ id: msg.id,
236
+ role: msg.role,
237
+ content: decryptedContent,
238
+ timestamp: msg.timestamp,
239
+ model: msg.model,
240
+ images: decryptedImages,
241
+ statistics: decryptedStatistics,
242
+ artifacts: decryptedArtifacts,
243
+ parentId: msg.parent_id,
244
+ branchIndex: msg.branch_index ?? 0,
245
+ isActive: msg.is_active !== 0,
246
+ siblingCount: siblingCount > 1 ? siblingCount : undefined,
247
+ };
248
+ }),
249
+ };
250
+ }
251
+ else {
252
+ // Fallback to JSON
253
+ const sessions = this.getAllSessions();
254
+ return sessions.find(s => s.id === sessionId);
255
+ }
256
+ }
257
+ saveSession(session, userId = 'default') {
258
+ if (this.useSQLite) {
259
+ const db = getDatabase();
260
+ // Use transaction for consistency
261
+ const transaction = db.transaction((session) => {
262
+ // Insert or update session
263
+ const sessionStmt = db.prepare(`
264
+ INSERT OR REPLACE INTO sessions (id, user_id, title, model, persona_id, created_at, updated_at)
265
+ VALUES (?, ?, ?, ?, ?, ?, ?)
266
+ `);
267
+ // Encrypt sensitive session data
268
+ const encryptedTitle = encryptionService.encrypt(session.title);
269
+ sessionStmt.run(session.id, userId, encryptedTitle, session.model, session.personaId || null, session.createdAt, session.updatedAt);
270
+ // Delete existing messages
271
+ const deleteMessagesStmt = db.prepare('DELETE FROM session_messages WHERE session_id = ?');
272
+ deleteMessagesStmt.run(session.id);
273
+ // Insert messages
274
+ if (session.messages && session.messages.length > 0) {
275
+ const insertMessageStmt = db.prepare(`
276
+ INSERT INTO session_messages (id, session_id, role, content, timestamp, message_index, model, images, statistics, artifacts, parent_id, branch_index, is_active)
277
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
278
+ `);
279
+ session.messages.forEach((message, index) => {
280
+ // Encrypt sensitive data before storing
281
+ const encryptedContent = encryptionService.encrypt(message.content);
282
+ const encryptedImages = message.images
283
+ ? encryptionService.encrypt(JSON.stringify(message.images))
284
+ : null;
285
+ const encryptedStatistics = message.statistics
286
+ ? encryptionService.encrypt(JSON.stringify(message.statistics))
287
+ : null;
288
+ const encryptedArtifacts = message.artifacts
289
+ ? encryptionService.encrypt(JSON.stringify(message.artifacts))
290
+ : null;
291
+ // Use the message's own ID if it has one, otherwise generate a new one
292
+ const messageId = message.id || uuidv4();
293
+ insertMessageStmt.run(messageId, session.id, message.role, encryptedContent, message.timestamp, index, message.model || null, encryptedImages, encryptedStatistics, encryptedArtifacts, message.parentId || null, message.branchIndex ?? 0, message.isActive !== false ? 1 : 0);
294
+ });
295
+ }
296
+ });
297
+ transaction(session);
298
+ }
299
+ else {
300
+ // Fallback to JSON
301
+ try {
302
+ const sessions = this.getAllSessions();
303
+ const existingIndex = sessions.findIndex(s => s.id === session.id);
304
+ if (existingIndex >= 0) {
305
+ sessions[existingIndex] = session;
306
+ }
307
+ else {
308
+ sessions.push(session);
309
+ }
310
+ fs.writeFileSync(this.sessionsFile, JSON.stringify(sessions, null, 2));
311
+ }
312
+ catch (error) {
313
+ console.error('Failed to save session to JSON:', error);
314
+ }
315
+ }
316
+ }
317
+ deleteSession(sessionId, userId = 'default') {
318
+ if (this.useSQLite) {
319
+ const db = getDatabase();
320
+ const stmt = db.prepare('DELETE FROM sessions WHERE id = ? AND user_id = ?');
321
+ const result = stmt.run(sessionId, userId);
322
+ return result.changes > 0;
323
+ }
324
+ else {
325
+ // Fallback to JSON
326
+ try {
327
+ const sessions = this.getAllSessions();
328
+ const filteredSessions = sessions.filter(s => s.id !== sessionId);
329
+ if (filteredSessions.length !== sessions.length) {
330
+ fs.writeFileSync(this.sessionsFile, JSON.stringify(filteredSessions, null, 2));
331
+ return true;
332
+ }
333
+ }
334
+ catch (error) {
335
+ console.error('Failed to delete session from JSON:', error);
336
+ }
337
+ }
338
+ return false;
339
+ }
340
+ clearAllSessions(userId = 'default') {
341
+ if (this.useSQLite) {
342
+ const db = getDatabase();
343
+ const stmt = db.prepare('DELETE FROM sessions WHERE user_id = ?');
344
+ const result = stmt.run(userId);
345
+ return result.changes;
346
+ }
347
+ else {
348
+ // Fallback to JSON
349
+ try {
350
+ const currentSessions = this.getAllSessions();
351
+ const deletedCount = currentSessions.length;
352
+ fs.writeFileSync(this.sessionsFile, JSON.stringify([], null, 2));
353
+ return deletedCount;
354
+ }
355
+ catch (error) {
356
+ console.error('Failed to clear all sessions from JSON:', error);
357
+ return 0;
358
+ }
359
+ }
360
+ }
361
+ // =================================
362
+ // PREFERENCES MANAGEMENT
363
+ // =================================
364
+ /**
365
+ * Safely decrypt and parse a preference value with proper error handling
366
+ */
367
+ safeDecryptPreference(key, value) {
368
+ try {
369
+ // Decrypt the preference value
370
+ const decryptedValue = encryptionService.decrypt(value);
371
+ try {
372
+ // Parse the decrypted value
373
+ return JSON.parse(decryptedValue);
374
+ }
375
+ catch (parseError) {
376
+ console.error(`Parsing error for preference key "${key}":`, parseError);
377
+ // Fallback to raw decrypted value
378
+ return decryptedValue;
379
+ }
380
+ }
381
+ catch (decryptError) {
382
+ console.error(`Decryption error for preference key "${key}":`, decryptError);
383
+ // Fallback to raw value (assuming unencrypted legacy data)
384
+ try {
385
+ return JSON.parse(value);
386
+ }
387
+ catch (parseError) {
388
+ console.error(`Parsing error for raw preference key "${key}":`, parseError);
389
+ return value;
390
+ }
391
+ }
392
+ }
393
+ getPreferences(userId) {
394
+ if (this.useSQLite) {
395
+ const db = getDatabase();
396
+ // If no userId provided, get the first user (single-user mode)
397
+ if (!userId) {
398
+ const firstUser = db.prepare('SELECT id FROM users LIMIT 1').get();
399
+ if (firstUser) {
400
+ userId = firstUser.id;
401
+ }
402
+ else {
403
+ return null; // No users found, return null
404
+ }
405
+ }
406
+ const stmt = db.prepare('SELECT key, value FROM user_preferences WHERE user_id = ?');
407
+ const rows = stmt.all(userId);
408
+ if (rows.length === 0)
409
+ return null;
410
+ const preferences = {};
411
+ rows.forEach(row => {
412
+ preferences[row.key] = this.safeDecryptPreference(row.key, row.value);
413
+ });
414
+ return preferences;
415
+ }
416
+ else {
417
+ // Fallback to JSON
418
+ try {
419
+ if (fs.existsSync(this.preferencesFile)) {
420
+ const data = fs.readFileSync(this.preferencesFile, 'utf8');
421
+ return JSON.parse(data);
422
+ }
423
+ }
424
+ catch (error) {
425
+ console.error('Failed to load preferences from JSON:', error);
426
+ }
427
+ }
428
+ return null;
429
+ }
430
+ savePreferences(preferences, userId) {
431
+ if (this.useSQLite) {
432
+ const db = getDatabase();
433
+ const now = Date.now();
434
+ // If no userId provided, get the first user (single-user mode)
435
+ if (!userId) {
436
+ const firstUser = db.prepare('SELECT id FROM users LIMIT 1').get();
437
+ if (firstUser) {
438
+ userId = firstUser.id;
439
+ }
440
+ else {
441
+ throw new Error('No users found in database');
442
+ }
443
+ }
444
+ const transaction = db.transaction((preferences) => {
445
+ // Delete existing preferences for this user
446
+ const deleteStmt = db.prepare('DELETE FROM user_preferences WHERE user_id = ?');
447
+ deleteStmt.run(userId);
448
+ // Insert new preferences
449
+ const insertStmt = db.prepare(`
450
+ INSERT INTO user_preferences (id, user_id, key, value, created_at, updated_at)
451
+ VALUES (?, ?, ?, ?, ?, ?)
452
+ `);
453
+ Object.entries(preferences).forEach(([key, value]) => {
454
+ // Skip undefined values - they would cause NOT NULL constraint errors
455
+ if (value === undefined) {
456
+ return;
457
+ }
458
+ // Encrypt the preference value before storing
459
+ const encryptedValue = encryptionService.encrypt(JSON.stringify(value));
460
+ insertStmt.run(uuidv4(), userId, key, encryptedValue, now, now);
461
+ });
462
+ });
463
+ transaction(preferences);
464
+ }
465
+ else {
466
+ // Fallback to JSON
467
+ try {
468
+ fs.writeFileSync(this.preferencesFile, JSON.stringify(preferences, null, 2));
469
+ }
470
+ catch (error) {
471
+ console.error('Failed to save preferences to JSON:', error);
472
+ }
473
+ }
474
+ }
475
+ // =================================
476
+ // DOCUMENT MANAGEMENT
477
+ // =================================
478
+ getAllDocuments(userId = 'default') {
479
+ if (this.useSQLite) {
480
+ const db = getDatabase();
481
+ const stmt = db.prepare(`
482
+ SELECT * FROM documents WHERE user_id = ? ORDER BY uploaded_at DESC
483
+ `);
484
+ const rows = stmt.all(userId);
485
+ return rows.map(row => {
486
+ // Decrypt document data
487
+ const decryptedTitle = row.title
488
+ ? encryptionService.decrypt(row.title)
489
+ : undefined;
490
+ const decryptedContent = row.content
491
+ ? encryptionService.decrypt(row.content)
492
+ : undefined;
493
+ const decryptedMetadata = row.metadata
494
+ ? JSON.parse(encryptionService.decrypt(row.metadata))
495
+ : undefined;
496
+ return {
497
+ id: row.id,
498
+ filename: row.filename,
499
+ title: decryptedTitle,
500
+ content: decryptedContent,
501
+ fileType: row.file_type,
502
+ size: row.size,
503
+ sessionId: row.session_id,
504
+ uploadedAt: row.uploaded_at,
505
+ createdAt: row.created_at,
506
+ metadata: decryptedMetadata,
507
+ };
508
+ });
509
+ }
510
+ else {
511
+ // Fallback to JSON
512
+ try {
513
+ if (fs.existsSync(this.documentsFile)) {
514
+ const data = fs.readFileSync(this.documentsFile, 'utf8');
515
+ return JSON.parse(data);
516
+ }
517
+ }
518
+ catch (error) {
519
+ console.error('Failed to load documents from JSON:', error);
520
+ }
521
+ }
522
+ return [];
523
+ }
524
+ getDocument(documentId, userId = 'default') {
525
+ if (this.useSQLite) {
526
+ const db = getDatabase();
527
+ const stmt = db.prepare('SELECT * FROM documents WHERE id = ? AND user_id = ?');
528
+ const row = stmt.get(documentId, userId);
529
+ if (!row)
530
+ return undefined;
531
+ // Decrypt document data
532
+ const decryptedTitle = row.title
533
+ ? encryptionService.decrypt(row.title)
534
+ : undefined;
535
+ const decryptedContent = row.content
536
+ ? encryptionService.decrypt(row.content)
537
+ : undefined;
538
+ const decryptedMetadata = row.metadata
539
+ ? JSON.parse(encryptionService.decrypt(row.metadata))
540
+ : undefined;
541
+ return {
542
+ id: row.id,
543
+ filename: row.filename,
544
+ title: decryptedTitle,
545
+ content: decryptedContent,
546
+ fileType: row.file_type,
547
+ size: row.size,
548
+ sessionId: row.session_id,
549
+ uploadedAt: row.uploaded_at,
550
+ createdAt: row.created_at,
551
+ metadata: decryptedMetadata,
552
+ };
553
+ }
554
+ else {
555
+ // Fallback to JSON
556
+ const documents = this.getAllDocuments();
557
+ return documents.find(d => d.id === documentId);
558
+ }
559
+ }
560
+ saveDocument(document, userId = 'default') {
561
+ if (this.useSQLite) {
562
+ const db = getDatabase();
563
+ const now = Date.now();
564
+ const stmt = db.prepare(`
565
+ INSERT OR REPLACE INTO documents
566
+ (id, user_id, filename, title, content, metadata, uploaded_at, created_at, updated_at)
567
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
568
+ `);
569
+ // Encrypt sensitive document data
570
+ const encryptedTitle = document.title
571
+ ? encryptionService.encrypt(document.title)
572
+ : null;
573
+ const encryptedContent = document.content
574
+ ? encryptionService.encrypt(document.content)
575
+ : null;
576
+ const encryptedMetadata = document.metadata
577
+ ? encryptionService.encrypt(JSON.stringify(document.metadata))
578
+ : null;
579
+ stmt.run(document.id, userId, document.filename, encryptedTitle, encryptedContent, encryptedMetadata, document.uploadedAt, document.createdAt || now, now);
580
+ }
581
+ else {
582
+ // Fallback to JSON
583
+ try {
584
+ const documents = this.getAllDocuments();
585
+ const existingIndex = documents.findIndex(d => d.id === document.id);
586
+ if (existingIndex >= 0) {
587
+ documents[existingIndex] = document;
588
+ }
589
+ else {
590
+ documents.push(document);
591
+ }
592
+ fs.writeFileSync(this.documentsFile, JSON.stringify(documents, null, 2));
593
+ }
594
+ catch (error) {
595
+ console.error('Failed to save document to JSON:', error);
596
+ }
597
+ }
598
+ }
599
+ deleteDocument(documentId, userId = 'default') {
600
+ if (this.useSQLite) {
601
+ const db = getDatabase();
602
+ const stmt = db.prepare('DELETE FROM documents WHERE id = ? AND user_id = ?');
603
+ const result = stmt.run(documentId, userId);
604
+ return result.changes > 0;
605
+ }
606
+ else {
607
+ // Fallback to JSON
608
+ try {
609
+ const documents = this.getAllDocuments();
610
+ const filteredDocuments = documents.filter(d => d.id !== documentId);
611
+ if (filteredDocuments.length !== documents.length) {
612
+ fs.writeFileSync(this.documentsFile, JSON.stringify(filteredDocuments, null, 2));
613
+ return true;
614
+ }
615
+ }
616
+ catch (error) {
617
+ console.error('Failed to delete document from JSON:', error);
618
+ }
619
+ }
620
+ return false;
621
+ }
622
+ // =================================
623
+ // DOCUMENT CHUNKS MANAGEMENT
624
+ // =================================
625
+ getDocumentChunks(documentId) {
626
+ if (this.useSQLite) {
627
+ const db = getDatabase();
628
+ const stmt = db.prepare(`
629
+ SELECT * FROM document_chunks WHERE document_id = ? ORDER BY chunk_index ASC
630
+ `);
631
+ const rows = stmt.all(documentId);
632
+ return rows.map(row => {
633
+ // Decrypt document chunk data
634
+ const decryptedContent = encryptionService.decrypt(row.content);
635
+ const decryptedEmbedding = row.embedding
636
+ ? JSON.parse(encryptionService.decrypt(row.embedding))
637
+ : undefined;
638
+ const decryptedMetadata = row.metadata
639
+ ? JSON.parse(encryptionService.decrypt(row.metadata))
640
+ : undefined;
641
+ return {
642
+ id: row.id,
643
+ documentId: row.document_id,
644
+ content: decryptedContent,
645
+ embedding: decryptedEmbedding,
646
+ chunkIndex: row.chunk_index,
647
+ startChar: row.start_char,
648
+ endChar: row.end_char,
649
+ metadata: decryptedMetadata,
650
+ };
651
+ });
652
+ }
653
+ else {
654
+ // Fallback to JSON
655
+ try {
656
+ if (fs.existsSync(this.documentChunksFile)) {
657
+ const data = fs.readFileSync(this.documentChunksFile, 'utf8');
658
+ const chunksData = JSON.parse(data);
659
+ return chunksData[documentId] || [];
660
+ }
661
+ }
662
+ catch (error) {
663
+ console.error('Failed to load document chunks from JSON:', error);
664
+ }
665
+ }
666
+ return [];
667
+ }
668
+ saveDocumentChunks(documentId, chunks) {
669
+ if (this.useSQLite) {
670
+ const db = getDatabase();
671
+ const now = Date.now();
672
+ const transaction = db.transaction((documentId, chunks) => {
673
+ // Delete existing chunks
674
+ const deleteStmt = db.prepare('DELETE FROM document_chunks WHERE document_id = ?');
675
+ deleteStmt.run(documentId);
676
+ // Insert new chunks
677
+ if (chunks.length > 0) {
678
+ const insertStmt = db.prepare(`
679
+ INSERT INTO document_chunks
680
+ (id, document_id, chunk_index, content, start_char, end_char, embedding, created_at)
681
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
682
+ `);
683
+ chunks.forEach(chunk => {
684
+ // Encrypt chunk data
685
+ const encryptedContent = encryptionService.encrypt(chunk.content);
686
+ const encryptedEmbedding = chunk.embedding
687
+ ? encryptionService.encrypt(JSON.stringify(chunk.embedding))
688
+ : null;
689
+ insertStmt.run(chunk.id, documentId, chunk.chunkIndex, encryptedContent, chunk.startChar || null, chunk.endChar || null, encryptedEmbedding, now);
690
+ });
691
+ }
692
+ });
693
+ transaction(documentId, chunks);
694
+ }
695
+ else {
696
+ // Fallback to JSON
697
+ try {
698
+ let chunksData = {};
699
+ if (fs.existsSync(this.documentChunksFile)) {
700
+ const data = fs.readFileSync(this.documentChunksFile, 'utf8');
701
+ chunksData = JSON.parse(data);
702
+ }
703
+ chunksData[documentId] = chunks;
704
+ fs.writeFileSync(this.documentChunksFile, JSON.stringify(chunksData, null, 2));
705
+ }
706
+ catch (error) {
707
+ console.error('Failed to save document chunks to JSON:', error);
708
+ }
709
+ }
710
+ }
711
+ deleteDocumentChunks(documentId) {
712
+ if (this.useSQLite) {
713
+ const db = getDatabase();
714
+ const stmt = db.prepare('DELETE FROM document_chunks WHERE document_id = ?');
715
+ const result = stmt.run(documentId);
716
+ return result.changes > 0;
717
+ }
718
+ else {
719
+ // Fallback to JSON
720
+ try {
721
+ if (fs.existsSync(this.documentChunksFile)) {
722
+ const data = fs.readFileSync(this.documentChunksFile, 'utf8');
723
+ const chunksData = JSON.parse(data);
724
+ if (chunksData[documentId]) {
725
+ delete chunksData[documentId];
726
+ fs.writeFileSync(this.documentChunksFile, JSON.stringify(chunksData, null, 2));
727
+ return true;
728
+ }
729
+ }
730
+ }
731
+ catch (error) {
732
+ console.error('Failed to delete document chunks from JSON:', error);
733
+ }
734
+ }
735
+ return false;
736
+ }
737
+ }
738
+ // Export singleton instance
739
+ const storageService = new StorageService();
740
+ export default storageService;
741
+ //# sourceMappingURL=storage.js.map