libre-webui 0.8.6 → 0.11.0

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 (314) hide show
  1. package/README.md +64 -46
  2. package/backend/dist/db.d.ts.map +1 -1
  3. package/backend/dist/db.js +21 -19
  4. package/backend/dist/db.js.map +1 -1
  5. package/backend/dist/index.d.ts.map +1 -1
  6. package/backend/dist/index.js +31 -944
  7. package/backend/dist/index.js.map +1 -1
  8. package/backend/dist/middleware/auth.d.ts.map +1 -1
  9. package/backend/dist/middleware/auth.js +7 -20
  10. package/backend/dist/middleware/auth.js.map +1 -1
  11. package/backend/dist/middleware/index.d.ts.map +1 -1
  12. package/backend/dist/middleware/index.js +4 -2
  13. package/backend/dist/middleware/index.js.map +1 -1
  14. package/backend/dist/models/personaModel.d.ts.map +1 -1
  15. package/backend/dist/models/personaModel.js +11 -9
  16. package/backend/dist/models/personaModel.js.map +1 -1
  17. package/backend/dist/routes/auth.d.ts.map +1 -1
  18. package/backend/dist/routes/auth.js +36 -14
  19. package/backend/dist/routes/auth.js.map +1 -1
  20. package/backend/dist/routes/chat.d.ts.map +1 -1
  21. package/backend/dist/routes/chat.js +61 -242
  22. package/backend/dist/routes/chat.js.map +1 -1
  23. package/backend/dist/routes/documents.d.ts.map +1 -1
  24. package/backend/dist/routes/documents.js +10 -8
  25. package/backend/dist/routes/documents.js.map +1 -1
  26. package/backend/dist/routes/embeddings.d.ts +3 -0
  27. package/backend/dist/routes/embeddings.d.ts.map +1 -0
  28. package/backend/dist/routes/embeddings.js +52 -0
  29. package/backend/dist/routes/embeddings.js.map +1 -0
  30. package/backend/dist/routes/imageGen.d.ts.map +1 -1
  31. package/backend/dist/routes/imageGen.js +9 -7
  32. package/backend/dist/routes/imageGen.js.map +1 -1
  33. package/backend/dist/routes/ollama.d.ts.map +1 -1
  34. package/backend/dist/routes/ollama.js +22 -239
  35. package/backend/dist/routes/ollama.js.map +1 -1
  36. package/backend/dist/routes/personas.d.ts.map +1 -1
  37. package/backend/dist/routes/personas.js +3 -1
  38. package/backend/dist/routes/personas.js.map +1 -1
  39. package/backend/dist/routes/plugins.d.ts.map +1 -1
  40. package/backend/dist/routes/plugins.js +7 -105
  41. package/backend/dist/routes/plugins.js.map +1 -1
  42. package/backend/dist/routes/tts.d.ts.map +1 -1
  43. package/backend/dist/routes/tts.js +7 -5
  44. package/backend/dist/routes/tts.js.map +1 -1
  45. package/backend/dist/routes/users.d.ts.map +1 -1
  46. package/backend/dist/routes/users.js +7 -5
  47. package/backend/dist/routes/users.js.map +1 -1
  48. package/backend/dist/services/assistantCompletionService.d.ts +26 -0
  49. package/backend/dist/services/assistantCompletionService.d.ts.map +1 -0
  50. package/backend/dist/services/assistantCompletionService.js +53 -0
  51. package/backend/dist/services/assistantCompletionService.js.map +1 -0
  52. package/backend/dist/services/authService.d.ts +2 -0
  53. package/backend/dist/services/authService.d.ts.map +1 -1
  54. package/backend/dist/services/authService.js +15 -7
  55. package/backend/dist/services/authService.js.map +1 -1
  56. package/backend/dist/services/chatGenerationService.d.ts +32 -0
  57. package/backend/dist/services/chatGenerationService.d.ts.map +1 -0
  58. package/backend/dist/services/chatGenerationService.js +118 -0
  59. package/backend/dist/services/chatGenerationService.js.map +1 -0
  60. package/backend/dist/services/chatRequestService.d.ts +55 -0
  61. package/backend/dist/services/chatRequestService.d.ts.map +1 -0
  62. package/backend/dist/services/chatRequestService.js +103 -0
  63. package/backend/dist/services/chatRequestService.js.map +1 -0
  64. package/backend/dist/services/chatService.d.ts +0 -1
  65. package/backend/dist/services/chatService.d.ts.map +1 -1
  66. package/backend/dist/services/chatService.js +17 -113
  67. package/backend/dist/services/chatService.js.map +1 -1
  68. package/backend/dist/services/documentService.d.ts.map +1 -1
  69. package/backend/dist/services/documentService.js +23 -21
  70. package/backend/dist/services/documentService.js.map +1 -1
  71. package/backend/dist/services/embeddingService.d.ts +9 -0
  72. package/backend/dist/services/embeddingService.d.ts.map +1 -0
  73. package/backend/dist/services/embeddingService.js +155 -0
  74. package/backend/dist/services/embeddingService.js.map +1 -0
  75. package/backend/dist/services/encryptionService.d.ts.map +1 -1
  76. package/backend/dist/services/encryptionService.js +26 -24
  77. package/backend/dist/services/encryptionService.js.map +1 -1
  78. package/backend/dist/services/galleryService.d.ts.map +1 -1
  79. package/backend/dist/services/galleryService.js +8 -6
  80. package/backend/dist/services/galleryService.js.map +1 -1
  81. package/backend/dist/services/memoryService.d.ts +2 -13
  82. package/backend/dist/services/memoryService.d.ts.map +1 -1
  83. package/backend/dist/services/memoryService.js +45 -211
  84. package/backend/dist/services/memoryService.js.map +1 -1
  85. package/backend/dist/services/memoryUtils.d.ts +29 -0
  86. package/backend/dist/services/memoryUtils.d.ts.map +1 -0
  87. package/backend/dist/services/memoryUtils.js +165 -0
  88. package/backend/dist/services/memoryUtils.js.map +1 -0
  89. package/backend/dist/services/mutationEngineService.d.ts.map +1 -1
  90. package/backend/dist/services/mutationEngineService.js +5 -3
  91. package/backend/dist/services/mutationEngineService.js.map +1 -1
  92. package/backend/dist/services/ollamaService.d.ts.map +1 -1
  93. package/backend/dist/services/ollamaService.js +67 -34
  94. package/backend/dist/services/ollamaService.js.map +1 -1
  95. package/backend/dist/services/personaService.d.ts.map +1 -1
  96. package/backend/dist/services/personaService.js +6 -2
  97. package/backend/dist/services/personaService.js.map +1 -1
  98. package/backend/dist/services/pluginCapabilityRegistryService.d.ts +11 -0
  99. package/backend/dist/services/pluginCapabilityRegistryService.d.ts.map +1 -0
  100. package/backend/dist/services/pluginCapabilityRegistryService.js +70 -0
  101. package/backend/dist/services/pluginCapabilityRegistryService.js.map +1 -0
  102. package/backend/dist/services/pluginCredentialsService.d.ts.map +1 -1
  103. package/backend/dist/services/pluginCredentialsService.js +13 -11
  104. package/backend/dist/services/pluginCredentialsService.js.map +1 -1
  105. package/backend/dist/services/pluginEmbeddingService.d.ts +26 -0
  106. package/backend/dist/services/pluginEmbeddingService.d.ts.map +1 -0
  107. package/backend/dist/services/pluginEmbeddingService.js +159 -0
  108. package/backend/dist/services/pluginEmbeddingService.js.map +1 -0
  109. package/backend/dist/services/pluginImageGenerationService.d.ts +29 -0
  110. package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -0
  111. package/backend/dist/services/pluginImageGenerationService.js +402 -0
  112. package/backend/dist/services/pluginImageGenerationService.js.map +1 -0
  113. package/backend/dist/services/pluginService.d.ts +21 -17
  114. package/backend/dist/services/pluginService.d.ts.map +1 -1
  115. package/backend/dist/services/pluginService.js +135 -1356
  116. package/backend/dist/services/pluginService.js.map +1 -1
  117. package/backend/dist/services/pluginTTSService.d.ts +28 -0
  118. package/backend/dist/services/pluginTTSService.d.ts.map +1 -0
  119. package/backend/dist/services/pluginTTSService.js +286 -0
  120. package/backend/dist/services/pluginTTSService.js.map +1 -0
  121. package/backend/dist/services/pluginVariablesService.d.ts.map +1 -1
  122. package/backend/dist/services/pluginVariablesService.js +7 -5
  123. package/backend/dist/services/pluginVariablesService.js.map +1 -1
  124. package/backend/dist/services/preferencesService.d.ts +1 -0
  125. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  126. package/backend/dist/services/preferencesService.js +21 -7
  127. package/backend/dist/services/preferencesService.js.map +1 -1
  128. package/backend/dist/services/simpleGitHubOAuth.d.ts.map +1 -1
  129. package/backend/dist/services/simpleGitHubOAuth.js +10 -8
  130. package/backend/dist/services/simpleGitHubOAuth.js.map +1 -1
  131. package/backend/dist/services/simpleHuggingFaceOAuth.d.ts.map +1 -1
  132. package/backend/dist/services/simpleHuggingFaceOAuth.js +11 -9
  133. package/backend/dist/services/simpleHuggingFaceOAuth.js.map +1 -1
  134. package/backend/dist/services/systemSettingsService.d.ts.map +1 -1
  135. package/backend/dist/services/systemSettingsService.js +4 -2
  136. package/backend/dist/services/systemSettingsService.js.map +1 -1
  137. package/backend/dist/services/titleGenerationService.d.ts +55 -0
  138. package/backend/dist/services/titleGenerationService.d.ts.map +1 -0
  139. package/backend/dist/services/titleGenerationService.js +121 -0
  140. package/backend/dist/services/titleGenerationService.js.map +1 -0
  141. package/backend/dist/services/turnstileService.d.ts +13 -0
  142. package/backend/dist/services/turnstileService.d.ts.map +1 -0
  143. package/backend/dist/services/turnstileService.js +79 -0
  144. package/backend/dist/services/turnstileService.js.map +1 -0
  145. package/backend/dist/storage.d.ts +2 -22
  146. package/backend/dist/storage.d.ts.map +1 -1
  147. package/backend/dist/storage.js +22 -170
  148. package/backend/dist/storage.js.map +1 -1
  149. package/backend/dist/storageMappers.d.ts +82 -0
  150. package/backend/dist/storageMappers.d.ts.map +1 -0
  151. package/backend/dist/storageMappers.js +85 -0
  152. package/backend/dist/storageMappers.js.map +1 -0
  153. package/backend/dist/test-encryption.js +20 -18
  154. package/backend/dist/test-encryption.js.map +1 -1
  155. package/backend/dist/types/index.d.ts +6 -0
  156. package/backend/dist/types/index.d.ts.map +1 -1
  157. package/backend/dist/types/index.js.map +1 -1
  158. package/backend/dist/utils/assistantBranching.d.ts +8 -0
  159. package/backend/dist/utils/assistantBranching.d.ts.map +1 -0
  160. package/backend/dist/utils/assistantBranching.js +30 -0
  161. package/backend/dist/utils/assistantBranching.js.map +1 -0
  162. package/backend/dist/utils/chatContext.d.ts +17 -0
  163. package/backend/dist/utils/chatContext.d.ts.map +1 -0
  164. package/backend/dist/utils/chatContext.js +82 -0
  165. package/backend/dist/utils/chatContext.js.map +1 -0
  166. package/backend/dist/utils/chatDocumentContext.d.ts +7 -0
  167. package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -0
  168. package/backend/dist/utils/chatDocumentContext.js +51 -0
  169. package/backend/dist/utils/chatDocumentContext.js.map +1 -0
  170. package/backend/dist/utils/logger.d.ts +12 -0
  171. package/backend/dist/utils/logger.d.ts.map +1 -0
  172. package/backend/dist/utils/logger.js +82 -0
  173. package/backend/dist/utils/logger.js.map +1 -0
  174. package/backend/dist/utils/ollamaLibrary.d.ts +17 -0
  175. package/backend/dist/utils/ollamaLibrary.d.ts.map +1 -0
  176. package/backend/dist/utils/ollamaLibrary.js +253 -0
  177. package/backend/dist/utils/ollamaLibrary.js.map +1 -0
  178. package/backend/dist/utils/ollamaStreaming.d.ts +19 -0
  179. package/backend/dist/utils/ollamaStreaming.d.ts.map +1 -0
  180. package/backend/dist/utils/ollamaStreaming.js +67 -0
  181. package/backend/dist/utils/ollamaStreaming.js.map +1 -0
  182. package/backend/dist/utils/packagePaths.d.ts +10 -0
  183. package/backend/dist/utils/packagePaths.d.ts.map +1 -0
  184. package/backend/dist/utils/packagePaths.js +81 -0
  185. package/backend/dist/utils/packagePaths.js.map +1 -0
  186. package/backend/dist/utils/pluginChatAdapter.d.ts +32 -0
  187. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -0
  188. package/backend/dist/utils/pluginChatAdapter.js +308 -0
  189. package/backend/dist/utils/pluginChatAdapter.js.map +1 -0
  190. package/backend/dist/utils/pluginChatContext.d.ts +23 -0
  191. package/backend/dist/utils/pluginChatContext.d.ts.map +1 -0
  192. package/backend/dist/utils/pluginChatContext.js +77 -0
  193. package/backend/dist/utils/pluginChatContext.js.map +1 -0
  194. package/backend/dist/utils/pluginResponse.d.ts +6 -0
  195. package/backend/dist/utils/pluginResponse.d.ts.map +1 -0
  196. package/backend/dist/utils/pluginResponse.js +70 -0
  197. package/backend/dist/utils/pluginResponse.js.map +1 -0
  198. package/backend/dist/utils/pluginStreamAdapter.d.ts +16 -0
  199. package/backend/dist/utils/pluginStreamAdapter.d.ts.map +1 -0
  200. package/backend/dist/utils/pluginStreamAdapter.js +141 -0
  201. package/backend/dist/utils/pluginStreamAdapter.js.map +1 -0
  202. package/backend/dist/utils/pluginStreaming.d.ts +24 -0
  203. package/backend/dist/utils/pluginStreaming.d.ts.map +1 -0
  204. package/backend/dist/utils/pluginStreaming.js +106 -0
  205. package/backend/dist/utils/pluginStreaming.js.map +1 -0
  206. package/backend/dist/utils/pluginUpload.d.ts +8 -0
  207. package/backend/dist/utils/pluginUpload.d.ts.map +1 -0
  208. package/backend/dist/utils/pluginUpload.js +51 -0
  209. package/backend/dist/utils/pluginUpload.js.map +1 -0
  210. package/backend/dist/utils/pluginValidation.d.ts +10 -0
  211. package/backend/dist/utils/pluginValidation.d.ts.map +1 -0
  212. package/backend/dist/utils/pluginValidation.js +94 -0
  213. package/backend/dist/utils/pluginValidation.js.map +1 -0
  214. package/backend/dist/utils/pluginVariableValidation.d.ts +10 -0
  215. package/backend/dist/utils/pluginVariableValidation.d.ts.map +1 -0
  216. package/backend/dist/utils/pluginVariableValidation.js +80 -0
  217. package/backend/dist/utils/pluginVariableValidation.js.map +1 -0
  218. package/backend/dist/utils/websocketMessages.d.ts +26 -0
  219. package/backend/dist/utils/websocketMessages.d.ts.map +1 -0
  220. package/backend/dist/utils/websocketMessages.js +74 -0
  221. package/backend/dist/utils/websocketMessages.js.map +1 -0
  222. package/backend/dist/websocketServer.d.ts +3 -0
  223. package/backend/dist/websocketServer.d.ts.map +1 -0
  224. package/backend/dist/websocketServer.js +332 -0
  225. package/backend/dist/websocketServer.js.map +1 -0
  226. package/backend/package.json +55 -0
  227. package/frontend/dist/assets/index-BxHDVIti.css +1 -0
  228. package/frontend/dist/assets/index-CDA2f43l.js +5 -0
  229. package/frontend/dist/assets/markdown-math-CYlHEDRF.css +1 -0
  230. package/frontend/dist/index.html +12 -15
  231. package/frontend/dist/js/ArtifactContainer-CA523BOS.js +1 -0
  232. package/frontend/dist/js/ArtifactDemoPage-C0p2-ccP.js +200 -0
  233. package/frontend/dist/js/ArtifactSlideOutPanel-BBYuYyr_.js +1 -0
  234. package/frontend/dist/js/Button-BmZQkAPD.js +1 -0
  235. package/frontend/dist/js/ChatPage-QSOhIynr.js +16 -0
  236. package/frontend/dist/js/FirstTimeSetup-pi-S4VU-.js +1 -0
  237. package/frontend/dist/js/GalleryPage-BPXB-D4f.js +1 -0
  238. package/frontend/dist/js/ModelManagerModals-D3SZV7CU.js +1 -0
  239. package/frontend/dist/js/ModelsPage-Fmqc1Zxb.js +2 -0
  240. package/frontend/dist/js/OptimizedSyntaxHighlighter-DlI4omE2.js +1 -0
  241. package/frontend/dist/js/PersonasPage-BuLOCG1Y.js +13 -0
  242. package/frontend/dist/js/RichMessageContent-Darvuw8F.js +3 -0
  243. package/frontend/dist/js/SettingsModal-CQhAeaia.js +1 -0
  244. package/frontend/dist/js/UserManagementPage-yX9vOase.js +1 -0
  245. package/frontend/dist/js/api-d05r9G59.js +15 -0
  246. package/frontend/dist/js/ar-BIL8U_dv.js +2 -0
  247. package/frontend/dist/js/artifactHtml-CRdkOCnz.js +27 -0
  248. package/frontend/dist/js/bn-BWQoWPn6.js +2 -0
  249. package/frontend/dist/js/cs-DOD1vscE.js +2 -0
  250. package/frontend/dist/js/da-DYPzSzX4.js +2 -0
  251. package/frontend/dist/js/de-CHd4b2Xf.js +2 -0
  252. package/frontend/dist/js/demoMode-BhEpMVh5.js +1 -0
  253. package/frontend/dist/js/es-BmDO6uNe.js +2 -0
  254. package/frontend/dist/js/fr-DW24sUV7.js +2 -0
  255. package/frontend/dist/js/hi-BRDq_y1B.js +2 -0
  256. package/frontend/dist/js/id-I_F2nstV.js +2 -0
  257. package/frontend/dist/js/is-Boj7-J12.js +2 -0
  258. package/frontend/dist/js/it-CNeHl9Fh.js +2 -0
  259. package/frontend/dist/js/ja-DrVG6G8s.js +2 -0
  260. package/frontend/dist/js/ko-BtKRwlHW.js +2 -0
  261. package/frontend/dist/js/logger-1THq8-96.js +1 -0
  262. package/frontend/dist/js/markdown-core-CEpaJrKd.js +16 -0
  263. package/frontend/dist/js/markdown-math-DAmpvM9y.js +261 -0
  264. package/frontend/dist/js/ms-Dp9MigoU.js +2 -0
  265. package/frontend/dist/js/nl-Bk9pGoCF.js +2 -0
  266. package/frontend/dist/js/pl-seE8_lLS.js +2 -0
  267. package/frontend/dist/js/pt-BAeBtldI.js +2 -0
  268. package/frontend/dist/js/{react-vendor-BUzyfgnZ.js → react-vendor-DtQrJICq.js} +4 -4
  269. package/frontend/dist/js/rolldown-runtime-QTnfLwEv.js +1 -0
  270. package/frontend/dist/js/ru-CkCpWFNb.js +2 -0
  271. package/frontend/dist/js/sv-BM7nqxmA.js +2 -0
  272. package/frontend/dist/js/syntax-highlight-B758xPeo.js +8 -0
  273. package/frontend/dist/js/th-vDIZbpa4.js +2 -0
  274. package/frontend/dist/js/tr-G2-XodhW.js +2 -0
  275. package/frontend/dist/js/ui-vendor-MCs1MDmN.js +177 -0
  276. package/frontend/dist/js/uk-DfVfZcBQ.js +2 -0
  277. package/frontend/dist/js/useBaseQuery-CrdrLcmR.js +1 -0
  278. package/frontend/dist/js/useQuery-DaEnYfWR.js +1 -0
  279. package/frontend/dist/js/utils-es0n-47H.js +1 -0
  280. package/frontend/dist/js/utils-vendor-B2ZBL57Y.js +9 -0
  281. package/frontend/dist/js/vi-Yp9yjb06.js +2 -0
  282. package/frontend/dist/js/zh-DtRb97we.js +2 -0
  283. package/package.json +16 -10
  284. package/plugins/llama-cpp.json +93 -0
  285. package/plugins/openclaw-agent.json +1 -1
  286. package/backend/dist/services/openclawSessionService.d.ts +0 -63
  287. package/backend/dist/services/openclawSessionService.d.ts.map +0 -1
  288. package/backend/dist/services/openclawSessionService.js +0 -307
  289. package/backend/dist/services/openclawSessionService.js.map +0 -1
  290. package/frontend/dist/assets/index-B1qNw5pA.js +0 -52
  291. package/frontend/dist/assets/index-DnG6512Q.css +0 -1
  292. package/frontend/dist/assets/ui-CsNe6lFV.css +0 -1
  293. package/frontend/dist/js/ArtifactContainer-Com7P5vK.js +0 -24
  294. package/frontend/dist/js/ArtifactDemoPage-Xd-Ragpf.js +0 -98
  295. package/frontend/dist/js/Button-D62PCbMW.js +0 -1
  296. package/frontend/dist/js/ChatPage-01JxVHRP.js +0 -15
  297. package/frontend/dist/js/GalleryPage-8nHGqKEu.js +0 -1
  298. package/frontend/dist/js/ModelsPage-C0MLVfwA.js +0 -1
  299. package/frontend/dist/js/OptimizedSyntaxHighlighter-DjnSqskO.js +0 -1
  300. package/frontend/dist/js/PersonasPage-D9YKnYz6.js +0 -13
  301. package/frontend/dist/js/UserManagementPage-b6NZdPTD.js +0 -1
  302. package/frontend/dist/js/api-B0pf9vdR.js +0 -1
  303. package/frontend/dist/js/api-BrzQvj_g.js +0 -3
  304. package/frontend/dist/js/appStore-k_e3JAM3.js +0 -2
  305. package/frontend/dist/js/authStore-BkobbhZS.js +0 -1
  306. package/frontend/dist/js/authStore-ge66S03D.js +0 -2
  307. package/frontend/dist/js/axios-CYrHjdt1.js +0 -1
  308. package/frontend/dist/js/demoMode-qyEGd_UM.js +0 -1
  309. package/frontend/dist/js/markdown-vendor-4lCPqxk5.js +0 -22
  310. package/frontend/dist/js/rolldown-runtime-DWy-cpC1.js +0 -1
  311. package/frontend/dist/js/ui-DuH1cD2f.js +0 -267
  312. package/frontend/dist/js/ui-vendor-BFOABH-A.js +0 -177
  313. package/frontend/dist/js/userService-BndgbS1C.js +0 -1
  314. package/frontend/dist/js/utils-vendor-MBozwMfr.js +0 -6
@@ -38,24 +38,6 @@ import cors from 'cors';
38
38
  import helmet from 'helmet';
39
39
  import morgan from 'morgan';
40
40
  import { createServer } from 'http';
41
- import { WebSocketServer } from 'ws';
42
- import { createRequire } from 'module';
43
- const require = createRequire(import.meta.url);
44
- // Try to load package.json from multiple locations
45
- let pkg = { version: '0.0.0' };
46
- try {
47
- // Development: ../package.json (backend/package.json doesn't exist, but root does via symlink)
48
- pkg = require('../package.json');
49
- }
50
- catch {
51
- try {
52
- // npm install: package is at node_modules/libre-webui, package.json is at root
53
- pkg = require('../../package.json');
54
- }
55
- catch {
56
- console.warn('Could not read version from package.json, using default');
57
- }
58
- }
59
41
  import { errorHandler, notFoundHandler, requestLogger, } from './middleware/index.js';
60
42
  import { optionalAuth } from './middleware/auth.js';
61
43
  import ollamaRoutes from './routes/ollama.js';
@@ -68,18 +50,17 @@ import usersRoutes from './routes/users.js';
68
50
  import personaRoutes from './routes/personas.js';
69
51
  import ttsRoutes from './routes/tts.js';
70
52
  import imageGenRoutes from './routes/imageGen.js';
53
+ import embeddingsRoutes from './routes/embeddings.js';
71
54
  import huggingfaceHubRoutes from './routes/huggingfaceHub.js';
72
55
  import ollamaService from './services/ollamaService.js';
73
- import chatService from './services/chatService.js';
74
56
  import { GitHubOAuthService } from './services/simpleGitHubOAuth.js';
75
57
  import { HuggingFaceOAuthService } from './services/simpleHuggingFaceOAuth.js';
76
- import pluginService from './services/pluginService.js';
77
- import preferencesService from './services/preferencesService.js';
78
- import documentService from './services/documentService.js';
79
- import openclawSessionService, { extractTextFromMessage, } from './services/openclawSessionService.js';
80
- import { mergeGenerationOptions } from './utils/generationUtils.js';
81
- import { verifyToken } from './utils/jwt.js';
58
+ import { loadAppPackage, resolveFrontendDist } from './utils/packagePaths.js';
59
+ import { registerWebSocketServer } from './websocketServer.js';
60
+ import { createLogger } from './utils/logger.js';
61
+ const pkg = loadAppPackage(import.meta.url);
82
62
  const app = express();
63
+ const logger = createLogger('server');
83
64
  // Trust proxy setting for running behind reverse proxies (Nginx, Caddy, etc.)
84
65
  // Set TRUST_PROXY=1 or TRUST_PROXY=loopback or TRUST_PROXY=uniquelocal etc.
85
66
  const trustProxy = process.env.TRUST_PROXY;
@@ -97,6 +78,8 @@ if (trustProxy) {
97
78
  }
98
79
  const isProduction = process.env.NODE_ENV === 'production';
99
80
  const port = process.env.PORT || (isProduction ? 8080 : 3001);
81
+ const hasTurnstile = Boolean(process.env.TURNSTILE_SITE_KEY?.trim()) &&
82
+ Boolean(process.env.TURNSTILE_SECRET_KEY?.trim());
100
83
  const corsOrigins = process.env.CORS_ORIGIN?.split(',') || [
101
84
  'http://localhost:5173',
102
85
  'http://localhost:3000',
@@ -123,7 +106,7 @@ const corsConfig = {
123
106
  const allowNetworkAccess = process.env.NODE_ENV !== 'production' ||
124
107
  process.env.DOCKER_ENV === 'true';
125
108
  const isNetworkOrigin = origin &&
126
- /^https?:\/\/(?:192\.168\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|127\.|localhost)/.test(origin);
109
+ /^https?:\/\/(?:192\.168\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.|127\.|localhost)/.test(origin);
127
110
  if (allowNetworkAccess && isNetworkOrigin) {
128
111
  callback(null, true);
129
112
  }
@@ -136,7 +119,9 @@ const corsConfig = {
136
119
  // Security middleware
137
120
  app.use(helmet({
138
121
  // COEP - disable in Docker/development to avoid proxy issues
139
- crossOriginEmbedderPolicy: process.env.NODE_ENV === 'production' && !process.env.DOCKER_ENV
122
+ crossOriginEmbedderPolicy: process.env.NODE_ENV === 'production' &&
123
+ !process.env.DOCKER_ENV &&
124
+ !hasTurnstile
140
125
  ? true
141
126
  : false,
142
127
  // Content Security Policy - Docker-aware configuration
@@ -145,6 +130,7 @@ app.use(helmet({
145
130
  defaultSrc: ["'self'"],
146
131
  scriptSrc: [
147
132
  "'self'",
133
+ 'https://challenges.cloudflare.com',
148
134
  ...(process.env.NODE_ENV === 'production'
149
135
  ? [] // Strict in production
150
136
  : ["'unsafe-inline'", "'unsafe-eval'"]), // Allow for dev tools
@@ -178,6 +164,7 @@ app.use(helmet({
178
164
  fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
179
165
  mediaSrc: ["'self'", 'data:', 'blob:'],
180
166
  objectSrc: ["'none'"],
167
+ frameSrc: ["'self'", 'https://challenges.cloudflare.com'],
181
168
  frameAncestors: ["'self'"],
182
169
  formAction: ["'self'"],
183
170
  upgradeInsecureRequests: process.env.NODE_ENV === 'production' && !process.env.DOCKER_ENV
@@ -336,6 +323,7 @@ app.use('/api/ollama', ollamaRateLimiter, ollamaRoutes);
336
323
  app.use('/api/chat', chatRateLimiter, optionalAuth, chatRoutes);
337
324
  app.use('/api/preferences', preferencesRateLimiter, optionalAuth, preferencesRoutes);
338
325
  app.use('/api/plugins', pluginRoutes);
326
+ app.use('/api/embeddings', embeddingsRoutes);
339
327
  app.use('/api/documents', documentsRateLimiter, documentRoutes);
340
328
  app.use('/api/personas', personasRateLimiter, optionalAuth, personaRoutes);
341
329
  app.use('/api/tts', ttsRateLimiter, optionalAuth, ttsRoutes);
@@ -345,25 +333,9 @@ app.use('/api/huggingface-hub', huggingfaceHubRoutes);
345
333
  if (process.env.NODE_ENV === 'production' ||
346
334
  process.env.SERVE_FRONTEND === 'true') {
347
335
  const pathModule = await import('path');
348
- const urlModule = await import('url');
349
- const fsModule = await import('fs');
350
- const __filename = urlModule.fileURLToPath(import.meta.url);
351
- const __dirname = pathModule.dirname(__filename);
352
- // Try multiple possible frontend locations
353
- const possiblePaths = [
354
- pathModule.join(__dirname, '../../frontend/dist'), // npm package structure
355
- pathModule.join(__dirname, '../../../frontend/dist'), // development
356
- pathModule.join(process.cwd(), 'frontend/dist'), // running from project root
357
- ];
358
- let frontendPath = '';
359
- for (const p of possiblePaths) {
360
- if (fsModule.existsSync(pathModule.join(p, 'index.html'))) {
361
- frontendPath = p;
362
- break;
363
- }
364
- }
336
+ const frontendPath = resolveFrontendDist(import.meta.url);
365
337
  if (frontendPath) {
366
- console.log(`Serving frontend from: ${frontendPath}`);
338
+ logger.info(`Serving frontend from: ${frontendPath}`);
367
339
  // Rate limiter for static files
368
340
  const staticRateLimiter = rateLimit({
369
341
  windowMs: 15 * 60 * 1000, // 15 minutes
@@ -388,7 +360,7 @@ if (process.env.NODE_ENV === 'production' ||
388
360
  });
389
361
  }
390
362
  else {
391
- console.warn('Frontend build not found. Run `npm run build:frontend` first.');
363
+ logger.warn('Frontend build not found. Run `npm run build:frontend` first.');
392
364
  }
393
365
  }
394
366
  // Error handling
@@ -396,897 +368,12 @@ app.use(notFoundHandler);
396
368
  app.use(errorHandler);
397
369
  // Create HTTP server
398
370
  const server = createServer(app);
399
- // WebSocket server for real-time chat streaming
400
- const wss = new WebSocketServer({
401
- server,
402
- path: '/ws',
403
- });
404
- wss.on('connection', (ws, req) => {
405
- console.log('WebSocket client connected');
406
- // Extract and verify auth token from query parameters
407
- let userId = 'default';
408
- try {
409
- const url = new URL(req.url || '', `http://${req.headers.host}`);
410
- const token = url.searchParams.get('token');
411
- if (token) {
412
- // Verify JWT token using the same logic as the auth middleware
413
- const decoded = verifyToken(token);
414
- userId = decoded.userId;
415
- console.log('WebSocket authenticated for user:', userId);
416
- }
417
- else {
418
- console.log('WebSocket connection without auth token, using default user');
419
- }
420
- }
421
- catch (error) {
422
- console.error('WebSocket auth error:', error);
423
- // Continue with default user for backward compatibility
424
- }
425
- ws.on('message', async (data) => {
426
- try {
427
- const message = JSON.parse(data.toString());
428
- if (message.type === 'chat_stream') {
429
- const { sessionId, content, images, format, options, assistantMessageId, regenerate, originalMessageId, isPrivate, model: privateModel, messageHistory, } = message.data;
430
- console.log('Backend: Received chat_stream for session:', sessionId, 'with images:', !!images, 'format:', !!format, 'regenerate:', !!regenerate, 'originalMessageId:', originalMessageId, 'isPrivate:', !!isPrivate);
431
- // For private sessions, skip database operations
432
- let session;
433
- if (isPrivate) {
434
- // Create a temporary in-memory session object for private mode
435
- session = {
436
- id: sessionId,
437
- model: privateModel,
438
- messages: [],
439
- title: 'Private Chat',
440
- createdAt: Date.now(),
441
- updatedAt: Date.now(),
442
- userId: userId,
443
- isPrivate: true,
444
- };
445
- }
446
- else {
447
- // Get session with user authentication
448
- session = chatService.getSession(sessionId, userId);
449
- if (!session) {
450
- console.log('Backend: Session not found:', sessionId, 'for user:', userId);
451
- ws.send(JSON.stringify({
452
- type: 'error',
453
- data: {
454
- error: 'Session not found',
455
- code: 'SESSION_NOT_FOUND',
456
- message: 'The requested session does not exist or does not belong to the current user. Please create a new session.',
457
- sessionId: sessionId,
458
- userId: userId,
459
- },
460
- }));
461
- return;
462
- }
463
- }
464
- // Add user message with images if provided (skip for regenerations and private sessions)
465
- let userMessage;
466
- if (!regenerate && !isPrivate) {
467
- userMessage = chatService.addMessage(sessionId, {
468
- role: 'user',
469
- content,
470
- images: images || undefined,
471
- }, userId);
472
- if (!userMessage) {
473
- ws.send(JSON.stringify({
474
- type: 'error',
475
- data: { error: 'Failed to add user message' },
476
- }));
477
- return;
478
- }
479
- // Send user message confirmation
480
- ws.send(JSON.stringify({
481
- type: 'user_message',
482
- data: userMessage,
483
- }));
484
- }
485
- else if (!regenerate && isPrivate) {
486
- // For private sessions, just send a confirmation without saving
487
- ws.send(JSON.stringify({
488
- type: 'user_message',
489
- data: {
490
- id: `private-msg-${Date.now()}`,
491
- role: 'user',
492
- content,
493
- images: images || undefined,
494
- timestamp: Date.now(),
495
- },
496
- }));
497
- }
498
- // RAG: Get relevant document context for the user's query
499
- const relevantContext = await documentService.getRelevantContext(content, sessionId);
500
- let enhancedContent = content;
501
- if (relevantContext.length > 0) {
502
- console.log(`Found ${relevantContext.length} relevant document chunks for query`);
503
- // Inject document context into the user message
504
- const contextString = relevantContext.join('\n\n---\n\n');
505
- enhancedContent = `Context from uploaded documents:\n\n${contextString}\n\n---\n\nUser question: ${content}`;
506
- // Update the user message with enhanced content that includes document context
507
- // We'll create a new message with the enhanced content for the AI model
508
- console.log('Enhanced user message with document context');
509
- }
510
- const contextMessages = isPrivate
511
- ? (messageHistory || []).concat([
512
- { role: 'user', content, images: images || undefined },
513
- ])
514
- : chatService.getMessagesForContext(sessionId);
515
- // Convert our messages to Ollama format
516
- const ollamaMessages = contextMessages.map((msg, index) => {
517
- const ollamaMessage = {
518
- role: msg.role,
519
- content: msg.content,
520
- };
521
- // Debug: Log what we're sending to Ollama
522
- if (msg.role === 'system') {
523
- console.log(`šŸš€ [DEBUG] Sending to Ollama - System message: "${msg.content.substring(0, 150)}${msg.content.length > 150 ? '...' : ''}"`);
524
- }
525
- // Use enhanced content for the last user message if we have document context
526
- if (msg.role === 'user' &&
527
- index === contextMessages.length - 1 &&
528
- relevantContext.length > 0) {
529
- ollamaMessage.content = enhancedContent;
530
- }
531
- // Process images: strip data URL prefix if present
532
- if (msg.images && msg.images.length > 0) {
533
- ollamaMessage.images = msg.images.map((img) => {
534
- // Strip data URL prefix if present (e.g., "data:image/png;base64,")
535
- if (typeof img === 'string' && img.includes(',')) {
536
- const base64Index = img.indexOf(',');
537
- if (base64Index !== -1) {
538
- return img.substring(base64Index + 1);
539
- }
540
- }
541
- return img;
542
- });
543
- }
544
- return ollamaMessage;
545
- });
546
- let assistantContent = '';
547
- let finalStatistics = undefined;
548
- console.log('Backend: Using assistantMessageId:', assistantMessageId);
549
- // Resolve the actual model name (handles persona IDs)
550
- let actualModelName = session.model;
551
- if (session.model.startsWith('persona:')) {
552
- try {
553
- const personaId = session.model.replace('persona:', '');
554
- console.log(`[WebSocket] DEBUG: Resolving persona ${personaId} for user ${userId}`);
555
- const { personaService } = await import('./services/personaService.js');
556
- // Try to get persona for the current user first, then fallback to 'default'
557
- let persona = await personaService.getPersonaById(personaId, userId);
558
- if (!persona && userId !== 'default') {
559
- console.log(`[WebSocket] DEBUG: Persona not found for user ${userId}, trying default user`);
560
- persona = await personaService.getPersonaById(personaId, 'default');
561
- }
562
- console.log(`[WebSocket] DEBUG: Persona lookup result:`, persona
563
- ? `Found persona with model: ${persona.model}`
564
- : 'Persona not found');
565
- if (persona && persona.model) {
566
- actualModelName = persona.model;
567
- console.log(`[WebSocket] Resolved persona ${personaId} to model: ${actualModelName}`);
568
- }
569
- else {
570
- console.warn(`[WebSocket] Persona ${personaId} not found, using original model: ${session.model}`);
571
- }
572
- }
573
- catch (error) {
574
- console.error(`[WebSocket] Error resolving persona model:`, error);
575
- }
576
- }
577
- // Check if there's an active plugin for this model
578
- console.log(`[WebSocket] Looking for plugin for model: ${actualModelName}`);
579
- const activePlugin = pluginService.getActivePluginForModel(actualModelName);
580
- console.log(`[WebSocket] Found plugin:`, activePlugin ? activePlugin.id : 'none');
581
- if (activePlugin) {
582
- console.log(`[WebSocket] Using plugin ${activePlugin.id} for model ${actualModelName}`);
583
- // Load plugin variables early — needed for session mode check
584
- const pluginVars = pluginService.getPluginVariables(activePlugin);
585
- // ---------------------------------------------------------------
586
- // OpenClaw Session Mode — route through WebSocket gateway
587
- // ---------------------------------------------------------------
588
- // OpenClaw session routing now handled via x-openclaw-session-key HTTP header
589
- // in pluginService — no WebSocket needed
590
- const isOpenClawSession = false; // Disabled: WS approach replaced by HTTP header
591
- if (isOpenClawSession) {
592
- console.log('[WebSocket] OpenClaw session mode: routing through gateway WS');
593
- try {
594
- // Ensure the gateway WS connection is established
595
- const endpoint = pluginVars.endpoint ||
596
- activePlugin.endpoint ||
597
- 'http://127.0.0.1:18789/v1/chat/completions';
598
- const apiKey = pluginService.getApiKey(activePlugin) || '';
599
- const ocSessionKey = pluginVars.session_key || 'main';
600
- if (!openclawSessionService.isConnected) {
601
- openclawSessionService.connect({
602
- gatewayUrl: endpoint,
603
- token: apiKey,
604
- sessionKey: ocSessionKey,
605
- });
606
- // Wait a bit for connection
607
- await new Promise(resolve => {
608
- const maxWait = 8000;
609
- const start = Date.now();
610
- const check = () => {
611
- if (openclawSessionService.isConnected) {
612
- resolve();
613
- }
614
- else if (Date.now() - start > maxWait) {
615
- resolve(); // proceed anyway, will error on send
616
- }
617
- else {
618
- setTimeout(check, 200);
619
- }
620
- };
621
- check();
622
- });
623
- }
624
- if (!openclawSessionService.isConnected) {
625
- throw new Error('Failed to connect to OpenClaw gateway WebSocket');
626
- }
627
- // Build the message to send
628
- const messageText = userMessage?.content || content;
629
- // Set up event listener for this run
630
- let totalContent = '';
631
- let currentRunId = null;
632
- let runDone = false;
633
- const toolCalls = [];
634
- const cleanup = openclawSessionService.subscribe((type, data) => {
635
- if (runDone)
636
- return;
637
- if (type === 'chat') {
638
- const chatEvent = data;
639
- if (currentRunId &&
640
- chatEvent.runId &&
641
- chatEvent.runId !== currentRunId)
642
- return;
643
- if (chatEvent.state === 'delta') {
644
- const text = extractTextFromMessage(chatEvent.message);
645
- if (typeof text === 'string' &&
646
- text.length > totalContent.length) {
647
- // Send incremental delta
648
- const newContent = text.slice(totalContent.length);
649
- totalContent = text;
650
- try {
651
- ws.send(JSON.stringify({
652
- type: 'assistant_chunk',
653
- data: {
654
- content: newContent,
655
- total: totalContent,
656
- done: false,
657
- messageId: assistantMessageId,
658
- },
659
- }));
660
- }
661
- catch {
662
- /* ws closed */
663
- }
664
- }
665
- }
666
- else if (chatEvent.state === 'final' ||
667
- chatEvent.state === 'aborted' ||
668
- chatEvent.state === 'error') {
669
- runDone = true;
670
- if (chatEvent.state === 'error') {
671
- const errMsg = chatEvent.errorMessage || 'Agent error';
672
- if (!totalContent)
673
- totalContent = `Error: ${errMsg}`;
674
- }
675
- // Append tool call summaries
676
- if (toolCalls.length > 0) {
677
- let toolContent = '\n\n---\n**šŸ”§ Tools Used:**\n';
678
- for (const tc of toolCalls) {
679
- const statusIcon = tc.phase === 'result' ? 'āœ…' : 'ā³';
680
- toolContent += `\n${statusIcon} **${tc.name}**`;
681
- if (tc.result) {
682
- const resultStr = typeof tc.result === 'string'
683
- ? tc.result
684
- : JSON.stringify(tc.result);
685
- if (resultStr.length <= 500) {
686
- toolContent += `\n<details><summary>Result</summary>\n\n\`\`\`\n${resultStr}\n\`\`\`\n</details>\n`;
687
- }
688
- else {
689
- toolContent += `\n<details><summary>Result (${resultStr.length} chars)</summary>\n\n\`\`\`\n${resultStr.slice(0, 500)}…\n\`\`\`\n</details>\n`;
690
- }
691
- }
692
- }
693
- totalContent += toolContent;
694
- }
695
- // Send final chunk
696
- try {
697
- ws.send(JSON.stringify({
698
- type: 'assistant_chunk',
699
- data: {
700
- content: '',
701
- total: totalContent,
702
- done: true,
703
- messageId: assistantMessageId,
704
- },
705
- }));
706
- }
707
- catch {
708
- /* ws closed */
709
- }
710
- }
711
- }
712
- else if (type === 'tool') {
713
- const toolEvent = data;
714
- // Track tool calls
715
- const existing = toolCalls.find(t => t.id === toolEvent.toolCallId);
716
- if (existing) {
717
- existing.phase = toolEvent.phase;
718
- if (toolEvent.result)
719
- existing.result =
720
- typeof toolEvent.result === 'string'
721
- ? toolEvent.result
722
- : JSON.stringify(toolEvent.result);
723
- }
724
- else {
725
- toolCalls.push({
726
- id: toolEvent.toolCallId,
727
- name: toolEvent.name,
728
- phase: toolEvent.phase,
729
- args: toolEvent.args
730
- ? JSON.stringify(toolEvent.args)
731
- : undefined,
732
- result: toolEvent.result
733
- ? typeof toolEvent.result === 'string'
734
- ? toolEvent.result
735
- : JSON.stringify(toolEvent.result)
736
- : undefined,
737
- });
738
- }
739
- // Send tool status to frontend
740
- // Send tool_status event for the activity indicator
741
- try {
742
- ws.send(JSON.stringify({
743
- type: 'tool_status',
744
- data: {
745
- toolCallId: toolEvent.toolCallId,
746
- name: toolEvent.name,
747
- phase: toolEvent.phase === 'start'
748
- ? 'running'
749
- : toolEvent.phase,
750
- },
751
- }));
752
- }
753
- catch {
754
- /* ws closed */
755
- }
756
- try {
757
- const toolStatusMsg = toolEvent.phase === 'start'
758
- ? `\n\nšŸ”§ *Using tool: ${toolEvent.name}…*\n`
759
- : '';
760
- if (toolStatusMsg) {
761
- totalContent += toolStatusMsg;
762
- ws.send(JSON.stringify({
763
- type: 'assistant_chunk',
764
- data: {
765
- content: toolStatusMsg,
766
- total: totalContent,
767
- done: false,
768
- messageId: assistantMessageId,
769
- },
770
- }));
771
- }
772
- }
773
- catch {
774
- /* ws closed */
775
- }
776
- }
777
- });
778
- // Send the message
779
- const result = await openclawSessionService.sendMessage(messageText, ocSessionKey);
780
- currentRunId = result.runId;
781
- // Wait for completion (max 5 minutes)
782
- await new Promise(resolve => {
783
- const timeout = setTimeout(() => {
784
- runDone = true;
785
- resolve();
786
- }, 300000);
787
- const checkDone = setInterval(() => {
788
- if (runDone) {
789
- clearInterval(checkDone);
790
- clearTimeout(timeout);
791
- resolve();
792
- }
793
- }, 100);
794
- });
795
- // Clean up listener
796
- cleanup();
797
- // Save the assistant message
798
- assistantContent = totalContent;
799
- }
800
- catch (error) {
801
- console.error('[WebSocket] OpenClaw session error:', error);
802
- const errorMsg = error instanceof Error ? error.message : String(error);
803
- assistantContent = `Error: ${errorMsg}`;
804
- ws.send(JSON.stringify({
805
- type: 'assistant_chunk',
806
- data: {
807
- content: assistantContent,
808
- total: assistantContent,
809
- done: true,
810
- messageId: assistantMessageId,
811
- },
812
- }));
813
- }
814
- // Save assistant message from session mode
815
- if (assistantContent && assistantMessageId) {
816
- if (isPrivate) {
817
- ws.send(JSON.stringify({
818
- type: 'assistant_complete',
819
- data: {
820
- id: assistantMessageId,
821
- role: 'assistant',
822
- content: assistantContent,
823
- model: session.model,
824
- timestamp: Date.now(),
825
- },
826
- }));
827
- }
828
- else {
829
- const assistantMessage = chatService.addMessage(sessionId, {
830
- role: 'assistant',
831
- content: assistantContent,
832
- model: session.model,
833
- id: assistantMessageId,
834
- }, userId);
835
- ws.send(JSON.stringify({
836
- type: 'assistant_complete',
837
- data: assistantMessage,
838
- }));
839
- }
840
- }
841
- return; // Exit early — handled via OpenClaw session
842
- }
843
- else {
844
- // ---------------------------------------------------------------
845
- // Standard plugin path (stateless HTTP completions)
846
- // ---------------------------------------------------------------
847
- try {
848
- // Get user's preferred generation options
849
- const userGenerationOptions = preferencesService.getGenerationOptions();
850
- // Merge user preferences with request options
851
- const mergedOptions = mergeGenerationOptions(userGenerationOptions, options);
852
- // Get messages for context
853
- const contextMessages = chatService.getMessagesForContext(sessionId);
854
- // For regenerations, the user message is already in context; for new messages, we need to add it
855
- let messagesForPlugin = regenerate
856
- ? contextMessages
857
- : contextMessages.concat([userMessage]);
858
- const systemPromptPrefix = pluginVars.system_prompt_prefix || '';
859
- const userName = pluginVars.user_name || '';
860
- // Prepend identity system message if configured
861
- if (systemPromptPrefix || userName) {
862
- let identityMsg = systemPromptPrefix;
863
- if (userName) {
864
- identityMsg = identityMsg
865
- ? `${identityMsg}\n\nThe user's name is: ${userName}`
866
- : `The user's name is: ${userName}`;
867
- }
868
- messagesForPlugin = [
869
- {
870
- id: 'system-identity',
871
- role: 'system',
872
- content: identityMsg,
873
- timestamp: Date.now(),
874
- },
875
- ...messagesForPlugin,
876
- ];
877
- }
878
- const shouldStream = pluginVars.stream ?? false;
879
- if (shouldStream) {
880
- // Real SSE streaming from plugin
881
- let totalContent = '';
882
- const toolCalls = [];
883
- // Tool activity tracking — single indicator, reused ID
884
- let pauseTimer = null;
885
- const PAUSE_TOOL_ID = 'tool-activity';
886
- let toolActivitySent = false;
887
- const PAUSE_THRESHOLD_MS = 2000;
888
- const sendToolStatus = (phase) => {
889
- try {
890
- ws.send(JSON.stringify({
891
- type: 'tool_status',
892
- data: {
893
- toolCallId: PAUSE_TOOL_ID,
894
- name: 'working',
895
- phase,
896
- },
897
- }));
898
- }
899
- catch {
900
- /* ws closed */
901
- }
902
- };
903
- const startPauseDetection = () => {
904
- if (pauseTimer)
905
- clearTimeout(pauseTimer);
906
- pauseTimer = setTimeout(() => {
907
- if (!toolActivitySent) {
908
- toolActivitySent = true;
909
- sendToolStatus('running');
910
- }
911
- }, PAUSE_THRESHOLD_MS);
912
- };
913
- startPauseDetection();
914
- for await (const chunk of pluginService.executePluginStreamRequest(actualModelName, messagesForPlugin, mergedOptions)) {
915
- if (chunk.type === 'content' && chunk.content) {
916
- // Content arrived — clear the tool indicator
917
- if (toolActivitySent) {
918
- sendToolStatus('done');
919
- toolActivitySent = false;
920
- }
921
- startPauseDetection();
922
- totalContent += chunk.content;
923
- ws.send(JSON.stringify({
924
- type: 'assistant_chunk',
925
- data: {
926
- content: chunk.content,
927
- total: totalContent,
928
- done: false,
929
- messageId: assistantMessageId,
930
- },
931
- }));
932
- }
933
- else if (chunk.type === 'tool_call' && chunk.toolCall) {
934
- toolCalls.push(chunk.toolCall);
935
- if (pauseTimer)
936
- clearTimeout(pauseTimer);
937
- toolActivitySent = true;
938
- // Send with real tool name but same stable ID
939
- try {
940
- ws.send(JSON.stringify({
941
- type: 'tool_status',
942
- data: {
943
- toolCallId: PAUSE_TOOL_ID,
944
- name: chunk.toolCall.name,
945
- phase: 'running',
946
- },
947
- }));
948
- }
949
- catch {
950
- /* ws closed */
951
- }
952
- }
953
- else if (chunk.type === 'done') {
954
- if (pauseTimer)
955
- clearTimeout(pauseTimer);
956
- if (toolActivitySent) {
957
- sendToolStatus('done');
958
- toolActivitySent = false;
959
- }
960
- // Append tool call info to content if present
961
- if (toolCalls.length > 0) {
962
- let toolContent = '\n\n---\n**šŸ”§ Tool Calls:**\n';
963
- for (const tc of toolCalls) {
964
- let argsFormatted = tc.arguments;
965
- try {
966
- argsFormatted = JSON.stringify(JSON.parse(tc.arguments), null, 2);
967
- }
968
- catch {
969
- /* keep raw */
970
- }
971
- toolContent += `\n**${tc.name}** (\`${tc.id}\`)\n\`\`\`json\n${argsFormatted}\n\`\`\`\n`;
972
- }
973
- totalContent += toolContent;
974
- }
975
- // Send final chunk
976
- ws.send(JSON.stringify({
977
- type: 'assistant_chunk',
978
- data: {
979
- content: '',
980
- total: totalContent,
981
- done: true,
982
- messageId: assistantMessageId,
983
- },
984
- }));
985
- }
986
- }
987
- assistantContent = totalContent;
988
- }
989
- else {
990
- // Non-streaming: use original request method
991
- const pluginResponse = await pluginService.executePluginRequest(actualModelName, messagesForPlugin, mergedOptions);
992
- if (!pluginResponse?.choices?.length) {
993
- throw new Error('Plugin returned empty or invalid response');
994
- }
995
- const choice = pluginResponse.choices[0];
996
- // Handle content that may be a string or array of content blocks
997
- const rawContent = choice?.message?.content;
998
- if (Array.isArray(rawContent)) {
999
- // Multimodal response: convert content blocks to markdown
1000
- const parts = [];
1001
- for (const block of rawContent) {
1002
- if (block.type === 'text' && block.text) {
1003
- parts.push(block.text);
1004
- }
1005
- else if (block.type === 'image_url' &&
1006
- block.image_url?.url) {
1007
- parts.push(`![image](${block.image_url.url})`);
1008
- }
1009
- }
1010
- assistantContent = parts.join('\n\n');
1011
- }
1012
- else {
1013
- assistantContent = rawContent || '';
1014
- }
1015
- // Render tool_calls from non-streaming response
1016
- const msgAny = choice?.message;
1017
- if (msgAny?.tool_calls &&
1018
- Array.isArray(msgAny.tool_calls) &&
1019
- msgAny.tool_calls.length > 0) {
1020
- let toolContent = '\n\n---\n**šŸ”§ Tool Calls:**\n';
1021
- for (const tc of msgAny.tool_calls) {
1022
- const name = tc.function?.name || 'unknown';
1023
- const id = tc.id || '';
1024
- let args = tc.function?.arguments || '';
1025
- try {
1026
- args = JSON.stringify(JSON.parse(args), null, 2);
1027
- }
1028
- catch {
1029
- /* keep raw */
1030
- }
1031
- toolContent += `\n**${name}** (\`${id}\`)\n\`\`\`json\n${args}\n\`\`\`\n`;
1032
- }
1033
- assistantContent += toolContent;
1034
- }
1035
- // Send the complete response as chunks to simulate streaming
1036
- const words = assistantContent.split(' ');
1037
- const BATCH_SIZE = 3;
1038
- for (let i = 0; i < words.length; i += BATCH_SIZE) {
1039
- const batch = words.slice(i, i + BATCH_SIZE);
1040
- const chunk = words.slice(0, i + batch.length).join(' ');
1041
- const isLast = i + BATCH_SIZE >= words.length;
1042
- ws.send(JSON.stringify({
1043
- type: 'assistant_chunk',
1044
- data: {
1045
- content: batch.join(' ') + (isLast ? '' : ' '),
1046
- total: chunk,
1047
- done: isLast,
1048
- messageId: assistantMessageId,
1049
- },
1050
- }));
1051
- if (!isLast) {
1052
- await new Promise(resolve => setTimeout(resolve, 100));
1053
- }
1054
- }
1055
- }
1056
- // Save the complete assistant message (skip for private sessions)
1057
- if (assistantContent && assistantMessageId) {
1058
- if (isPrivate) {
1059
- // For private sessions, just send completion without saving
1060
- console.log('Backend: Private session - skipping message save');
1061
- ws.send(JSON.stringify({
1062
- type: 'assistant_complete',
1063
- data: {
1064
- id: assistantMessageId,
1065
- role: 'assistant',
1066
- content: assistantContent,
1067
- model: session.model,
1068
- timestamp: Date.now(),
1069
- },
1070
- }));
1071
- }
1072
- else {
1073
- console.log('Backend: Saving complete assistant message with ID:', assistantMessageId, 'regenerate:', !!regenerate);
1074
- // Calculate branching fields if this is a regeneration
1075
- let branchingFields = {};
1076
- if (regenerate && originalMessageId) {
1077
- // Find the original message to get its parentId or use its ID as parent
1078
- const originalMsg = session.messages.find(m => m.id === originalMessageId);
1079
- const parentId = originalMsg?.parentId || originalMessageId;
1080
- // Count existing siblings to determine branch index
1081
- const siblingCount = session.messages.filter(m => m.id === parentId || m.parentId === parentId).length;
1082
- branchingFields = {
1083
- parentId,
1084
- branchIndex: siblingCount, // New branch gets next index
1085
- isActive: true,
1086
- };
1087
- console.log('Backend: Setting branching fields:', branchingFields);
1088
- }
1089
- const assistantMessage = chatService.addMessage(sessionId, {
1090
- role: 'assistant',
1091
- content: assistantContent,
1092
- model: session.model,
1093
- id: assistantMessageId,
1094
- ...branchingFields,
1095
- }, userId);
1096
- console.log('Backend: Assistant message saved:', !!assistantMessage);
1097
- // Send completion signal
1098
- ws.send(JSON.stringify({
1099
- type: 'assistant_complete',
1100
- data: assistantMessage,
1101
- }));
1102
- }
1103
- }
1104
- return; // Exit early since we handled the request via plugin
1105
- }
1106
- catch (pluginError) {
1107
- const err = pluginError instanceof Error
1108
- ? pluginError
1109
- : new Error(String(pluginError));
1110
- const errWithResponse = pluginError;
1111
- console.error('Plugin failed, falling back to Ollama:', err.message);
1112
- if (errWithResponse &&
1113
- typeof errWithResponse === 'object' &&
1114
- 'response' in errWithResponse) {
1115
- const resp = errWithResponse.response;
1116
- console.error('Plugin HTTP response status:', resp.status);
1117
- console.error('Plugin HTTP response data:', JSON.stringify(resp.data));
1118
- }
1119
- if ('cause' in err) {
1120
- console.error('Plugin error cause:', err.cause);
1121
- }
1122
- // If a plugin was found but failed, don't fall through to Ollama
1123
- if (activePlugin) {
1124
- console.error(`[WebSocket] Plugin "${activePlugin.name}" failed for model ${actualModelName}, not falling back to Ollama`);
1125
- ws.send(JSON.stringify({
1126
- type: 'error',
1127
- message: `Plugin request failed: ${err.message}`,
1128
- }));
1129
- return;
1130
- }
1131
- // Continue to Ollama fallback below (no plugin was matched)
1132
- }
1133
- } // close else (standard plugin path)
1134
- }
1135
- console.log(`[WebSocket] No plugin found, using Ollama for model: ${actualModelName}`);
1136
- // Reuse the actualModelName variable that was already resolved above
1137
- // If we're here, it means either there was no plugin or plugin failed
1138
- // The actualModelName was already resolved in the earlier code block
1139
- // Get user's preferred generation options
1140
- const userGenerationOptions = preferencesService.getGenerationOptions();
1141
- // Merge user preferences with request options
1142
- const mergedOptions = mergeGenerationOptions(userGenerationOptions, options);
1143
- // Create chat request with advanced features
1144
- const chatRequest = {
1145
- model: actualModelName,
1146
- messages: ollamaMessages,
1147
- stream: true,
1148
- options: mergedOptions,
1149
- };
1150
- // Add structured output format if specified
1151
- if (format) {
1152
- chatRequest.format = format;
1153
- }
1154
- // Stream response from Ollama using chat completion
1155
- await ollamaService.generateChatStreamResponse(chatRequest, chunk => {
1156
- if (chunk.message?.content) {
1157
- assistantContent += chunk.message.content;
1158
- // Send streaming chunk with the provided message ID
1159
- ws.send(JSON.stringify({
1160
- type: 'assistant_chunk',
1161
- data: {
1162
- content: chunk.message.content,
1163
- total: assistantContent,
1164
- done: chunk.done,
1165
- messageId: assistantMessageId,
1166
- },
1167
- }));
1168
- }
1169
- // Capture final statistics when streaming is done
1170
- if (chunk.done) {
1171
- finalStatistics = {
1172
- total_duration: chunk.total_duration,
1173
- load_duration: chunk.load_duration,
1174
- prompt_eval_count: chunk.prompt_eval_count,
1175
- prompt_eval_duration: chunk.prompt_eval_duration,
1176
- eval_count: chunk.eval_count,
1177
- eval_duration: chunk.eval_duration,
1178
- created_at: chunk.created_at,
1179
- model: chunk.model,
1180
- };
1181
- // Calculate tokens per second if we have the necessary data
1182
- if (chunk.eval_count && chunk.eval_duration) {
1183
- finalStatistics.tokens_per_second =
1184
- Math.round((chunk.eval_count / (chunk.eval_duration / 1e9)) * 100) / 100;
1185
- }
1186
- }
1187
- }, error => {
1188
- ws.send(JSON.stringify({
1189
- type: 'error',
1190
- data: { error: error.message },
1191
- }));
1192
- }, () => {
1193
- // Save the complete assistant message with the provided ID (skip for private sessions)
1194
- if (assistantContent && assistantMessageId) {
1195
- if (isPrivate) {
1196
- // For private sessions, just send completion without saving
1197
- console.log('Backend: Private session - skipping Ollama message save');
1198
- ws.send(JSON.stringify({
1199
- type: 'assistant_complete',
1200
- data: {
1201
- content: assistantContent,
1202
- role: 'assistant',
1203
- timestamp: Date.now(),
1204
- messageId: assistantMessageId,
1205
- statistics: finalStatistics,
1206
- },
1207
- }));
1208
- }
1209
- else {
1210
- console.log('Backend: Saving complete assistant message with ID:', assistantMessageId, 'regenerate:', !!regenerate);
1211
- // Calculate branching fields if this is a regeneration
1212
- let branchingFields = {};
1213
- if (regenerate && originalMessageId) {
1214
- // Find the original message to get its parentId or use its ID as parent
1215
- const originalMsg = session.messages.find(m => m.id === originalMessageId);
1216
- const parentId = originalMsg?.parentId || originalMessageId;
1217
- // Count existing siblings to determine branch index
1218
- const siblingCount = session.messages.filter(m => m.id === parentId || m.parentId === parentId).length;
1219
- branchingFields = {
1220
- parentId,
1221
- branchIndex: siblingCount, // New branch gets next index
1222
- isActive: true,
1223
- };
1224
- console.log('Backend: Setting branching fields:', branchingFields);
1225
- }
1226
- console.log('Backend: About to save assistant message:', {
1227
- sessionId,
1228
- messageId: assistantMessageId,
1229
- contentLength: assistantContent.length,
1230
- hasBranchingFields: Object.keys(branchingFields).length > 0,
1231
- branchingFields,
1232
- });
1233
- const assistantMessage = chatService.addMessage(sessionId, {
1234
- role: 'assistant',
1235
- content: assistantContent,
1236
- model: session.model,
1237
- id: assistantMessageId,
1238
- statistics: finalStatistics,
1239
- ...branchingFields,
1240
- }, userId);
1241
- console.log('Backend: Assistant message saved:', !!assistantMessage, assistantMessage
1242
- ? {
1243
- id: assistantMessage.id,
1244
- contentLength: assistantMessage.content.length,
1245
- }
1246
- : 'FAILED TO SAVE');
1247
- // Send completion signal with statistics
1248
- ws.send(JSON.stringify({
1249
- type: 'assistant_complete',
1250
- data: {
1251
- content: assistantContent,
1252
- role: 'assistant',
1253
- timestamp: Date.now(),
1254
- messageId: assistantMessageId,
1255
- statistics: finalStatistics,
1256
- ...branchingFields,
1257
- },
1258
- }));
1259
- }
1260
- }
1261
- });
1262
- }
1263
- }
1264
- catch (error) {
1265
- console.error('WebSocket error:', error);
1266
- const errorMessage = error instanceof Error ? error.message : String(error);
1267
- ws.send(JSON.stringify({
1268
- type: 'error',
1269
- data: { error: errorMessage },
1270
- }));
1271
- }
1272
- });
1273
- ws.on('close', () => {
1274
- console.log('WebSocket client disconnected');
1275
- });
1276
- ws.on('error', error => {
1277
- console.error('WebSocket error:', error);
1278
- });
1279
- // Send initial connection confirmation
1280
- ws.send(JSON.stringify({
1281
- type: 'connected',
1282
- data: { message: 'Connected to Libre WebUI' },
1283
- }));
1284
- });
371
+ registerWebSocketServer(server);
1285
372
  // Start server
1286
373
  server.listen({ port, host: '0.0.0.0' }, () => {
1287
374
  const url = `http://localhost:${port}`;
1288
- console.log(`\nšŸš€ Libre WebUI v${pkg.version}`);
1289
- console.log(` ${url}\n`);
375
+ logger.info(`Libre WebUI v${pkg.version}`);
376
+ logger.info(url);
1290
377
  // Open browser in production mode
1291
378
  if (process.env.SERVE_FRONTEND === 'true') {
1292
379
  import('child_process').then(({ exec }) => {
@@ -1304,38 +391,38 @@ server.listen({ port, host: '0.0.0.0' }, () => {
1304
391
  const githubConfigured = githubOAuth.isConfigured();
1305
392
  const hfConfigured = hfOAuth.isConfigured();
1306
393
  if (githubConfigured || hfConfigured) {
1307
- console.log('šŸ” SSO Configuration:');
394
+ logger.info('SSO configuration:');
1308
395
  if (githubConfigured) {
1309
- console.log(' āœ… GitHub OAuth configured and ready');
396
+ logger.info('GitHub OAuth configured and ready');
1310
397
  }
1311
398
  if (hfConfigured) {
1312
- console.log(' āœ… Hugging Face OAuth configured and ready');
399
+ logger.info('Hugging Face OAuth configured and ready');
1313
400
  }
1314
401
  }
1315
402
  else {
1316
- console.log('ā„¹ļø No SSO providers configured (optional)');
403
+ logger.info('No SSO providers configured (optional)');
1317
404
  }
1318
405
  // Check Ollama connection on startup
1319
406
  ollamaService.isHealthy().then(isHealthy => {
1320
407
  if (isHealthy) {
1321
- console.log('āœ… Ollama service is connected and ready');
408
+ logger.info('Ollama service is connected and ready');
1322
409
  }
1323
410
  else {
1324
- console.log("āš ļø Ollama service is not available - make sure it's running on http://localhost:11434");
411
+ logger.warn("Ollama service is not available - make sure it's running on http://localhost:11434");
1325
412
  }
1326
413
  });
1327
414
  });
1328
415
  // Graceful shutdown
1329
416
  process.on('SIGTERM', () => {
1330
- console.log('SIGTERM signal received: closing HTTP server');
417
+ logger.info('SIGTERM signal received: closing HTTP server');
1331
418
  server.close(() => {
1332
- console.log('HTTP server closed');
419
+ logger.info('HTTP server closed');
1333
420
  });
1334
421
  });
1335
422
  process.on('SIGINT', () => {
1336
- console.log('SIGINT signal received: closing HTTP server');
423
+ logger.info('SIGINT signal received: closing HTTP server');
1337
424
  server.close(() => {
1338
- console.log('HTTP server closed');
425
+ logger.info('HTTP server closed');
1339
426
  });
1340
427
  });
1341
428
  export default app;