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,767 @@
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 express from 'express';
18
+ import rateLimit from 'express-rate-limit';
19
+ import chatService from '../services/chatService.js';
20
+ import ollamaService from '../services/ollamaService.js';
21
+ import pluginService from '../services/pluginService.js';
22
+ import preferencesService from '../services/preferencesService.js';
23
+ import documentService from '../services/documentService.js';
24
+ import { personaService } from '../services/personaService.js';
25
+ import { authenticate } from '../middleware/auth.js';
26
+ import { mergeGenerationOptions, extractStatistics, } from '../utils/generationUtils.js';
27
+ import { getErrorMessage, } from '../types/index.js';
28
+ const router = express.Router();
29
+ // Rate limiter for chat routes: 60 requests per minute (reasonable for chat)
30
+ const chatRateLimiter = rateLimit({
31
+ windowMs: 1 * 60 * 1000, // 1 minute
32
+ max: 60, // limit each IP to 60 requests per minute
33
+ message: {
34
+ success: false,
35
+ message: 'Too many chat requests, please slow down',
36
+ },
37
+ standardHeaders: true,
38
+ legacyHeaders: false,
39
+ });
40
+ // Apply rate limiter to all chat routes
41
+ router.use(chatRateLimiter);
42
+ // Apply authentication middleware to all chat routes
43
+ router.use(authenticate);
44
+ // Helper function to resolve the actual model name from session model
45
+ // If the model is a persona ID (starts with "persona:"), extract the actual model name
46
+ async function resolveActualModelName(sessionModel, userId = 'default') {
47
+ if (sessionModel.startsWith('persona:')) {
48
+ try {
49
+ const personaId = sessionModel.replace('persona:', '');
50
+ // Try to get persona for the current user first, then fallback to 'default'
51
+ let persona = await personaService.getPersonaById(personaId, userId);
52
+ if (!persona && userId !== 'default') {
53
+ console.log(`[DEBUG] Persona not found for user ${userId}, trying default user`);
54
+ persona = await personaService.getPersonaById(personaId, 'default');
55
+ }
56
+ if (persona && persona.model) {
57
+ console.log(`[DEBUG] Resolved persona ${personaId} to model: ${persona.model}`);
58
+ return persona.model;
59
+ }
60
+ else {
61
+ console.warn(`[DEBUG] Persona ${personaId} not found, falling back to session model`);
62
+ return sessionModel;
63
+ }
64
+ }
65
+ catch (error) {
66
+ console.error(`[DEBUG] Error resolving persona model:`, error);
67
+ return sessionModel;
68
+ }
69
+ }
70
+ return sessionModel;
71
+ }
72
+ // Get all chat sessions
73
+ router.get('/sessions', async (req, res) => {
74
+ try {
75
+ const userId = req.user?.userId || 'default';
76
+ const sessions = chatService.getAllSessions(userId);
77
+ res.json({
78
+ success: true,
79
+ data: sessions,
80
+ });
81
+ }
82
+ catch (error) {
83
+ res.status(500).json({
84
+ success: false,
85
+ error: getErrorMessage(error, 'Failed to load sessions'),
86
+ });
87
+ }
88
+ });
89
+ // Create a new chat session
90
+ router.post('/sessions', async (req, res) => {
91
+ try {
92
+ const { model, title, personaId } = req.body;
93
+ if (!model) {
94
+ res.status(400).json({
95
+ success: false,
96
+ error: 'Model is required',
97
+ });
98
+ return;
99
+ }
100
+ const userId = req.user?.userId || 'default';
101
+ // Extract persona ID from model string if it starts with "persona:"
102
+ let extractedPersonaId = personaId;
103
+ if (model.startsWith('persona:') && !extractedPersonaId) {
104
+ extractedPersonaId = model.replace('persona:', '');
105
+ console.log(`[DEBUG] Extracted personaId from model: ${extractedPersonaId}`);
106
+ }
107
+ const session = await chatService.createSession(model, title, userId, extractedPersonaId);
108
+ res.json({
109
+ success: true,
110
+ data: session,
111
+ });
112
+ }
113
+ catch (error) {
114
+ res.status(500).json({
115
+ success: false,
116
+ error: getErrorMessage(error, 'Failed to create session'),
117
+ });
118
+ }
119
+ });
120
+ // Get a specific chat session
121
+ router.get('/sessions/:sessionId', async (req, res) => {
122
+ try {
123
+ const { sessionId } = req.params;
124
+ const userId = req.user?.userId || 'default';
125
+ const session = chatService.getSession(sessionId, userId);
126
+ if (!session) {
127
+ res.status(404).json({
128
+ success: false,
129
+ error: 'Session not found',
130
+ });
131
+ return;
132
+ }
133
+ res.json({
134
+ success: true,
135
+ data: session,
136
+ });
137
+ }
138
+ catch (error) {
139
+ res.status(500).json({
140
+ success: false,
141
+ error: getErrorMessage(error, 'Failed to get session'),
142
+ });
143
+ }
144
+ });
145
+ // Update a chat session
146
+ router.put('/sessions/:sessionId', async (req, res) => {
147
+ try {
148
+ const { sessionId } = req.params;
149
+ const updates = req.body;
150
+ const userId = req.user?.userId || 'default';
151
+ const updatedSession = await chatService.updateSession(sessionId, updates, userId);
152
+ if (!updatedSession) {
153
+ res.status(404).json({
154
+ success: false,
155
+ error: 'Session not found',
156
+ });
157
+ return;
158
+ }
159
+ res.json({
160
+ success: true,
161
+ data: updatedSession,
162
+ });
163
+ }
164
+ catch (error) {
165
+ res.status(500).json({
166
+ success: false,
167
+ error: getErrorMessage(error, 'Failed to update session'),
168
+ });
169
+ }
170
+ });
171
+ // Update a message in a chat session
172
+ router.put('/sessions/:sessionId/messages/:messageId', async (req, res) => {
173
+ try {
174
+ const { sessionId, messageId } = req.params;
175
+ const updates = req.body;
176
+ const userId = req.user?.userId || 'default';
177
+ const updatedMessage = chatService.updateMessage(sessionId, messageId, updates, userId);
178
+ if (!updatedMessage) {
179
+ res.status(404).json({
180
+ success: false,
181
+ error: 'Session or message not found',
182
+ });
183
+ return;
184
+ }
185
+ res.json({
186
+ success: true,
187
+ data: updatedMessage,
188
+ });
189
+ }
190
+ catch (error) {
191
+ res.status(500).json({
192
+ success: false,
193
+ error: getErrorMessage(error, 'Failed to update message'),
194
+ });
195
+ }
196
+ });
197
+ // Delete a chat session
198
+ router.delete('/sessions/:sessionId', async (req, res) => {
199
+ try {
200
+ const { sessionId } = req.params;
201
+ const userId = req.user?.userId || 'default';
202
+ const deleted = chatService.deleteSession(sessionId, userId);
203
+ if (!deleted) {
204
+ res.status(404).json({
205
+ success: false,
206
+ error: 'Session not found',
207
+ });
208
+ return;
209
+ }
210
+ res.json({
211
+ success: true,
212
+ message: 'Session deleted successfully',
213
+ });
214
+ }
215
+ catch (error) {
216
+ res.status(500).json({
217
+ success: false,
218
+ error: getErrorMessage(error, 'Failed to delete session'),
219
+ });
220
+ }
221
+ });
222
+ // Clear all chat sessions
223
+ router.delete('/sessions', async (req, res) => {
224
+ try {
225
+ const userId = req.user?.userId || 'default';
226
+ chatService.clearAllSessions(userId);
227
+ res.json({
228
+ success: true,
229
+ message: 'All chat sessions cleared successfully',
230
+ });
231
+ }
232
+ catch (error) {
233
+ res.status(500).json({
234
+ success: false,
235
+ error: getErrorMessage(error, 'Failed to clear sessions'),
236
+ });
237
+ }
238
+ });
239
+ // Add a message to a session
240
+ router.post('/sessions/:sessionId/messages', async (req, res) => {
241
+ try {
242
+ const { sessionId } = req.params;
243
+ const { role, content, id, model } = req.body;
244
+ if (!role || !content) {
245
+ res.status(400).json({
246
+ success: false,
247
+ error: 'Role and content are required',
248
+ });
249
+ return;
250
+ }
251
+ const userId = req.user?.userId || 'default';
252
+ const session = chatService.getSession(sessionId, userId);
253
+ if (!session) {
254
+ res.status(404).json({
255
+ success: false,
256
+ error: 'Session not found',
257
+ });
258
+ return;
259
+ }
260
+ const message = chatService.addMessage(sessionId, {
261
+ role,
262
+ content,
263
+ model,
264
+ id, // Use provided ID if available
265
+ }, userId);
266
+ if (!message) {
267
+ res.status(500).json({
268
+ success: false,
269
+ error: 'Failed to add message',
270
+ });
271
+ return;
272
+ }
273
+ res.json({
274
+ success: true,
275
+ data: message,
276
+ });
277
+ }
278
+ catch (error) {
279
+ res.status(500).json({
280
+ success: false,
281
+ error: getErrorMessage(error, 'Failed to add message'),
282
+ });
283
+ }
284
+ });
285
+ // Generate a chat response
286
+ router.post('/sessions/:sessionId/generate', async (req, res) => {
287
+ try {
288
+ const { sessionId } = req.params;
289
+ const { message, options = {} } = req.body;
290
+ if (!message) {
291
+ res.status(400).json({
292
+ success: false,
293
+ error: 'Message is required',
294
+ });
295
+ return;
296
+ }
297
+ const userId = req.user?.userId || 'default';
298
+ const session = chatService.getSession(sessionId, userId);
299
+ if (!session) {
300
+ res.status(404).json({
301
+ success: false,
302
+ error: 'Session not found',
303
+ });
304
+ return;
305
+ }
306
+ // Add user message to session
307
+ const userMessage = chatService.addMessage(sessionId, {
308
+ role: 'user',
309
+ content: message,
310
+ }, userId);
311
+ if (!userMessage) {
312
+ res.status(500).json({
313
+ success: false,
314
+ error: 'Failed to add user message',
315
+ });
316
+ return;
317
+ }
318
+ // Check if document search is available and enabled
319
+ let documentContext = '';
320
+ try {
321
+ const preferences = preferencesService.getPreferences();
322
+ if (preferences.embeddingSettings?.enabled) {
323
+ console.log(`[DEBUG] Embeddings enabled, searching documents for: "${message}"`);
324
+ const relevantDocuments = await documentService.searchDocuments(message, sessionId);
325
+ console.log(`[DEBUG] Found ${relevantDocuments.length} relevant document chunks`);
326
+ if (relevantDocuments.length > 0) {
327
+ // Get document info for each chunk
328
+ const documentsMap = new Map();
329
+ for (const chunk of relevantDocuments) {
330
+ if (!documentsMap.has(chunk.documentId)) {
331
+ const doc = documentService.getDocument(chunk.documentId);
332
+ documentsMap.set(chunk.documentId, doc);
333
+ }
334
+ }
335
+ documentContext =
336
+ '\n\n--- RELEVANT DOCUMENTS ---\n' +
337
+ relevantDocuments
338
+ .map((chunk, index) => {
339
+ const doc = documentsMap.get(chunk.documentId);
340
+ const docTitle = doc ? doc.filename : 'Unknown Document';
341
+ return `Document ${index + 1}: ${docTitle} (chunk ${chunk.chunkIndex + 1})\n${chunk.content}\n`;
342
+ })
343
+ .join('\n---\n') +
344
+ '\n--- END DOCUMENTS ---\n\n';
345
+ console.log(`[DEBUG] Added ${documentContext.length} characters of document context`);
346
+ }
347
+ else {
348
+ console.log(`[DEBUG] No relevant documents found for query: "${message}"`);
349
+ }
350
+ }
351
+ else {
352
+ console.log(`[DEBUG] Embeddings disabled, skipping document search`);
353
+ }
354
+ }
355
+ catch (error) {
356
+ console.error('[DEBUG] Error during document search:', error);
357
+ // Continue without document context if search fails
358
+ }
359
+ // Convert chat messages to Ollama format and handle persona system prompts
360
+ let ollamaMessages = session.messages.map((msg) => ({
361
+ role: msg.role,
362
+ content: msg.content,
363
+ }));
364
+ // Inject persona instructions if session has a persona
365
+ if (session.personaId) {
366
+ try {
367
+ const persona = await personaService.getPersonaById(session.personaId, userId);
368
+ if (persona && persona.parameters.system_prompt) {
369
+ // Remove any existing system messages and replace with persona's system prompt
370
+ ollamaMessages = ollamaMessages.filter(msg => msg.role !== 'system');
371
+ ollamaMessages.unshift({
372
+ role: 'system',
373
+ content: persona.parameters.system_prompt,
374
+ });
375
+ console.log(`[DEBUG] Replaced system messages with persona instructions for: ${persona.name}`);
376
+ }
377
+ }
378
+ catch (error) {
379
+ console.error('[DEBUG] Error loading persona:', error);
380
+ // Continue without persona if loading fails
381
+ }
382
+ }
383
+ // Add the new user message with document context if available
384
+ const userMessageContent = documentContext
385
+ ? `${documentContext}User question: ${message}`
386
+ : message;
387
+ ollamaMessages.push({
388
+ role: 'user',
389
+ content: userMessageContent,
390
+ });
391
+ let response;
392
+ let assistantContent;
393
+ // Get user's preferred generation options
394
+ const userGenerationOptions = preferencesService.getGenerationOptions();
395
+ // Merge user preferences with request options (request options take precedence)
396
+ const mergedOptions = mergeGenerationOptions(userGenerationOptions, options);
397
+ // Resolve the actual model name (handles persona IDs)
398
+ const actualModelName = await resolveActualModelName(session.model, userId);
399
+ console.log(`[DEBUG] Resolved model from "${session.model}" to "${actualModelName}"`);
400
+ // Prepare common chat request for Ollama (used in both fallback and direct cases)
401
+ const chatRequest = {
402
+ model: actualModelName,
403
+ messages: ollamaMessages,
404
+ stream: false,
405
+ options: mergedOptions,
406
+ };
407
+ // Check if there's an active plugin for this model
408
+ console.log(`[DEBUG] Looking for plugin for model: ${actualModelName}`);
409
+ const activePlugin = pluginService.getActivePluginForModel(actualModelName);
410
+ console.log(`[DEBUG] Found plugin:`, activePlugin ? activePlugin.id : 'none');
411
+ if (activePlugin) {
412
+ console.log(`[DEBUG] Using plugin ${activePlugin.id} for model ${actualModelName}`);
413
+ try {
414
+ // Use plugin for generation
415
+ const pluginResponse = await pluginService.executePluginRequest(actualModelName, session.messages.concat([userMessage]), options);
416
+ // Convert plugin response to our format
417
+ assistantContent = pluginResponse.choices[0]?.message?.content || '';
418
+ // Create a mock response in Ollama format
419
+ response = {
420
+ model: actualModelName,
421
+ created_at: new Date().toISOString(),
422
+ message: {
423
+ role: 'assistant',
424
+ content: assistantContent,
425
+ },
426
+ done: true,
427
+ };
428
+ }
429
+ catch (pluginError) {
430
+ console.error('Plugin failed, falling back to Ollama:', pluginError);
431
+ // Fallback to Ollama
432
+ response = await ollamaService.generateChatResponse(chatRequest);
433
+ assistantContent = response.message.content;
434
+ }
435
+ }
436
+ else {
437
+ console.log(`[DEBUG] No plugin found, using Ollama for model: ${actualModelName}`);
438
+ // Use Ollama directly
439
+ response = await ollamaService.generateChatResponse(chatRequest);
440
+ assistantContent = response.message.content;
441
+ }
442
+ // Add assistant response to session with statistics
443
+ const statistics = extractStatistics(response);
444
+ const assistantMessage = chatService.addMessage(sessionId, {
445
+ role: 'assistant',
446
+ content: assistantContent,
447
+ model: session.model,
448
+ statistics,
449
+ }, userId);
450
+ if (!assistantMessage) {
451
+ res.status(500).json({
452
+ success: false,
453
+ error: 'Failed to add assistant message',
454
+ });
455
+ return;
456
+ }
457
+ res.json({
458
+ success: true,
459
+ data: assistantMessage,
460
+ });
461
+ }
462
+ catch (error) {
463
+ res.status(500).json({
464
+ success: false,
465
+ error: getErrorMessage(error, 'Failed to generate response'),
466
+ });
467
+ }
468
+ });
469
+ // Generate a chat response with streaming
470
+ router.post('/sessions/:sessionId/generate/stream', async (req, res) => {
471
+ try {
472
+ const { sessionId } = req.params;
473
+ const { message, options = {} } = req.body;
474
+ if (!message) {
475
+ res.status(400).json({
476
+ success: false,
477
+ error: 'Message is required',
478
+ });
479
+ return;
480
+ }
481
+ const userId = req.user?.userId || 'default';
482
+ const session = chatService.getSession(sessionId, userId);
483
+ if (!session) {
484
+ res.status(404).json({
485
+ success: false,
486
+ error: 'Session not found',
487
+ });
488
+ return;
489
+ }
490
+ // Set up SSE headers
491
+ res.setHeader('Content-Type', 'text/event-stream');
492
+ res.setHeader('Cache-Control', 'no-cache');
493
+ res.setHeader('Connection', 'keep-alive');
494
+ res.setHeader('Access-Control-Allow-Origin', '*');
495
+ // Add user message to session
496
+ const userMessage = chatService.addMessage(sessionId, {
497
+ role: 'user',
498
+ content: message,
499
+ }, userId);
500
+ if (!userMessage) {
501
+ res.write(`data: ${JSON.stringify({ error: 'Failed to add user message' })}\n\n`);
502
+ res.end();
503
+ return;
504
+ }
505
+ // Convert chat messages to Ollama format and handle persona system prompts
506
+ let ollamaMessages = session.messages.map((msg) => ({
507
+ role: msg.role,
508
+ content: msg.content,
509
+ }));
510
+ // Inject persona instructions if session has a persona
511
+ if (session.personaId) {
512
+ try {
513
+ const persona = await personaService.getPersonaById(session.personaId, userId);
514
+ if (persona && persona.parameters.system_prompt) {
515
+ // Remove any existing system messages and replace with persona's system prompt
516
+ ollamaMessages = ollamaMessages.filter(msg => msg.role !== 'system');
517
+ ollamaMessages.unshift({
518
+ role: 'system',
519
+ content: persona.parameters.system_prompt,
520
+ });
521
+ console.log(`[DEBUG] Replaced system messages with persona instructions for streaming: ${persona.name}`);
522
+ }
523
+ }
524
+ catch (error) {
525
+ console.error('[DEBUG] Error loading persona for streaming:', error);
526
+ // Continue without persona if loading fails
527
+ }
528
+ }
529
+ // Add the new user message
530
+ ollamaMessages.push({
531
+ role: 'user',
532
+ content: message,
533
+ });
534
+ // Get user's preferred generation options
535
+ const userGenerationOptions = preferencesService.getGenerationOptions();
536
+ // Merge user preferences with request options (request options take precedence)
537
+ const mergedOptions = mergeGenerationOptions(userGenerationOptions, options);
538
+ // Resolve the actual model name (handles persona IDs)
539
+ const actualModelName = await resolveActualModelName(session.model, userId);
540
+ console.log(`[DEBUG] Streaming - Resolved model from "${session.model}" to "${actualModelName}"`);
541
+ const chatRequest = {
542
+ model: actualModelName,
543
+ messages: ollamaMessages,
544
+ stream: true,
545
+ options: mergedOptions,
546
+ };
547
+ let fullResponse = '';
548
+ // Generate streaming response using Ollama
549
+ await ollamaService.generateChatStreamResponse(chatRequest, chunk => {
550
+ // Send chunk to client
551
+ res.write(`data: ${JSON.stringify({
552
+ type: 'chunk',
553
+ content: chunk.message.content || '',
554
+ done: chunk.done,
555
+ })}\n\n`);
556
+ // Accumulate response content
557
+ if (chunk.message.content) {
558
+ fullResponse += chunk.message.content;
559
+ }
560
+ }, error => {
561
+ res.write(`data: ${JSON.stringify({ type: 'error', error: error.message })}\n\n`);
562
+ res.end();
563
+ }, () => {
564
+ // Add complete assistant response to session
565
+ if (fullResponse) {
566
+ chatService.addMessage(sessionId, {
567
+ role: 'assistant',
568
+ content: fullResponse,
569
+ model: session.model,
570
+ }, userId);
571
+ }
572
+ res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`);
573
+ res.end();
574
+ });
575
+ }
576
+ catch (error) {
577
+ res.write(`data: ${JSON.stringify({ type: 'error', error: getErrorMessage(error, 'Failed to generate stream response') })}\n\n`);
578
+ res.end();
579
+ }
580
+ });
581
+ // Generate a title for a chat session based on the first message
582
+ router.post('/sessions/:sessionId/generate-title', async (req, res) => {
583
+ try {
584
+ const { sessionId } = req.params;
585
+ const { model, message } = req.body;
586
+ if (!model) {
587
+ res.status(400).json({
588
+ success: false,
589
+ error: 'Model is required for title generation',
590
+ });
591
+ return;
592
+ }
593
+ if (!message) {
594
+ res.status(400).json({
595
+ success: false,
596
+ error: 'Message is required for title generation',
597
+ });
598
+ return;
599
+ }
600
+ const userId = req.user?.userId || 'default';
601
+ const session = chatService.getSession(sessionId, userId);
602
+ if (!session) {
603
+ res.status(404).json({
604
+ success: false,
605
+ error: 'Session not found',
606
+ });
607
+ return;
608
+ }
609
+ // Generate title using a simple prompt
610
+ const titlePrompt = `Generate a very short, concise title (3-6 words max) for a chat that starts with this message. Only respond with the title, nothing else. No quotes, no punctuation at the end. Do not use any markdown formatting.
611
+
612
+ Message: "${message.substring(0, 500)}"
613
+
614
+ Title:`;
615
+ try {
616
+ const response = await ollamaService.generateResponse({
617
+ model: model,
618
+ prompt: titlePrompt,
619
+ stream: false,
620
+ options: {
621
+ temperature: 0.3,
622
+ num_predict: 20,
623
+ stop: ['\n', '.', '!', '?'],
624
+ },
625
+ });
626
+ // Clean up the generated title
627
+ let title = response.response
628
+ .trim()
629
+ .replace(/^["']|["']$/g, '') // Remove quotes
630
+ .replace(/[.!?]+$/, '') // Remove trailing punctuation
631
+ .trim();
632
+ // Fallback if title is empty or too long
633
+ if (!title || title.length > 50) {
634
+ title = message.substring(0, 30) + (message.length > 30 ? '...' : '');
635
+ }
636
+ // Update the session with the new title
637
+ const updatedSession = await chatService.updateSession(sessionId, { title }, userId);
638
+ if (!updatedSession) {
639
+ res.status(500).json({
640
+ success: false,
641
+ error: 'Failed to update session title',
642
+ });
643
+ return;
644
+ }
645
+ res.json({
646
+ success: true,
647
+ data: { title },
648
+ });
649
+ }
650
+ catch (ollamaError) {
651
+ console.error('Error generating title with Ollama:', ollamaError);
652
+ // Fallback to using the first part of the message as title
653
+ const fallbackTitle = message.substring(0, 30) + (message.length > 30 ? '...' : '');
654
+ await chatService.updateSession(sessionId, { title: fallbackTitle }, userId);
655
+ res.json({
656
+ success: true,
657
+ data: { title: fallbackTitle },
658
+ });
659
+ }
660
+ }
661
+ catch (error) {
662
+ res.status(500).json({
663
+ success: false,
664
+ error: getErrorMessage(error, 'Failed to generate title'),
665
+ });
666
+ }
667
+ });
668
+ /**
669
+ * Switch to a different branch of a message
670
+ * POST /api/chat/sessions/:sessionId/messages/:messageId/branch
671
+ */
672
+ router.post('/sessions/:sessionId/messages/:messageId/branch', authenticate, chatRateLimiter, async (req, res) => {
673
+ try {
674
+ const { sessionId, messageId } = req.params;
675
+ const { branchIndex } = req.body;
676
+ const userId = req.user?.userId || 'default';
677
+ if (typeof branchIndex !== 'number') {
678
+ res.status(400).json({
679
+ success: false,
680
+ error: 'branchIndex is required and must be a number',
681
+ });
682
+ return;
683
+ }
684
+ const updatedMessage = chatService.switchMessageBranch(sessionId, messageId, branchIndex, userId);
685
+ if (!updatedMessage) {
686
+ res.status(404).json({
687
+ success: false,
688
+ error: 'Message or branch not found',
689
+ });
690
+ return;
691
+ }
692
+ // Return the updated session
693
+ const session = chatService.getSession(sessionId, userId);
694
+ res.json({
695
+ success: true,
696
+ data: session,
697
+ });
698
+ }
699
+ catch (error) {
700
+ console.error('Switch branch error:', error);
701
+ res.status(500).json({
702
+ success: false,
703
+ error: getErrorMessage(error, 'Failed to switch branch'),
704
+ });
705
+ }
706
+ });
707
+ /**
708
+ * Get all branches for a message
709
+ * GET /api/chat/sessions/:sessionId/messages/:messageId/branches
710
+ */
711
+ router.get('/sessions/:sessionId/messages/:messageId/branches', authenticate, chatRateLimiter, async (req, res) => {
712
+ try {
713
+ const { sessionId, messageId } = req.params;
714
+ const userId = req.user?.userId || 'default';
715
+ const branches = chatService.getMessageBranches(sessionId, messageId, userId);
716
+ res.json({
717
+ success: true,
718
+ data: branches,
719
+ });
720
+ }
721
+ catch (error) {
722
+ console.error('Get branches error:', error);
723
+ res.status(500).json({
724
+ success: false,
725
+ error: getErrorMessage(error, 'Failed to get branches'),
726
+ });
727
+ }
728
+ });
729
+ /**
730
+ * Create a new branch for a message (for regeneration)
731
+ * POST /api/chat/sessions/:sessionId/messages/:messageId/branches
732
+ */
733
+ router.post('/sessions/:sessionId/messages/:messageId/branches', authenticate, chatRateLimiter, async (req, res) => {
734
+ try {
735
+ const { sessionId, messageId } = req.params;
736
+ const userId = req.user?.userId || 'default';
737
+ const messageData = req.body;
738
+ if (!messageData || !messageData.role || !messageData.content) {
739
+ res.status(400).json({
740
+ success: false,
741
+ error: 'Message data with role and content is required',
742
+ });
743
+ return;
744
+ }
745
+ const newBranch = chatService.createMessageBranch(sessionId, messageId, messageData, userId);
746
+ if (!newBranch) {
747
+ res.status(404).json({
748
+ success: false,
749
+ error: 'Failed to create branch - session or message not found',
750
+ });
751
+ return;
752
+ }
753
+ res.json({
754
+ success: true,
755
+ data: newBranch,
756
+ });
757
+ }
758
+ catch (error) {
759
+ console.error('Create branch error:', error);
760
+ res.status(500).json({
761
+ success: false,
762
+ error: getErrorMessage(error, 'Failed to create branch'),
763
+ });
764
+ }
765
+ });
766
+ export default router;
767
+ //# sourceMappingURL=chat.js.map