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.
- package/README.md +64 -46
- package/backend/dist/db.d.ts.map +1 -1
- package/backend/dist/db.js +21 -19
- package/backend/dist/db.js.map +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +31 -944
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/middleware/auth.d.ts.map +1 -1
- package/backend/dist/middleware/auth.js +7 -20
- package/backend/dist/middleware/auth.js.map +1 -1
- package/backend/dist/middleware/index.d.ts.map +1 -1
- package/backend/dist/middleware/index.js +4 -2
- package/backend/dist/middleware/index.js.map +1 -1
- package/backend/dist/models/personaModel.d.ts.map +1 -1
- package/backend/dist/models/personaModel.js +11 -9
- package/backend/dist/models/personaModel.js.map +1 -1
- package/backend/dist/routes/auth.d.ts.map +1 -1
- package/backend/dist/routes/auth.js +36 -14
- package/backend/dist/routes/auth.js.map +1 -1
- package/backend/dist/routes/chat.d.ts.map +1 -1
- package/backend/dist/routes/chat.js +61 -242
- package/backend/dist/routes/chat.js.map +1 -1
- package/backend/dist/routes/documents.d.ts.map +1 -1
- package/backend/dist/routes/documents.js +10 -8
- package/backend/dist/routes/documents.js.map +1 -1
- package/backend/dist/routes/embeddings.d.ts +3 -0
- package/backend/dist/routes/embeddings.d.ts.map +1 -0
- package/backend/dist/routes/embeddings.js +52 -0
- package/backend/dist/routes/embeddings.js.map +1 -0
- package/backend/dist/routes/imageGen.d.ts.map +1 -1
- package/backend/dist/routes/imageGen.js +9 -7
- package/backend/dist/routes/imageGen.js.map +1 -1
- package/backend/dist/routes/ollama.d.ts.map +1 -1
- package/backend/dist/routes/ollama.js +22 -239
- package/backend/dist/routes/ollama.js.map +1 -1
- package/backend/dist/routes/personas.d.ts.map +1 -1
- package/backend/dist/routes/personas.js +3 -1
- package/backend/dist/routes/personas.js.map +1 -1
- package/backend/dist/routes/plugins.d.ts.map +1 -1
- package/backend/dist/routes/plugins.js +7 -105
- package/backend/dist/routes/plugins.js.map +1 -1
- package/backend/dist/routes/tts.d.ts.map +1 -1
- package/backend/dist/routes/tts.js +7 -5
- package/backend/dist/routes/tts.js.map +1 -1
- package/backend/dist/routes/users.d.ts.map +1 -1
- package/backend/dist/routes/users.js +7 -5
- package/backend/dist/routes/users.js.map +1 -1
- package/backend/dist/services/assistantCompletionService.d.ts +26 -0
- package/backend/dist/services/assistantCompletionService.d.ts.map +1 -0
- package/backend/dist/services/assistantCompletionService.js +53 -0
- package/backend/dist/services/assistantCompletionService.js.map +1 -0
- package/backend/dist/services/authService.d.ts +2 -0
- package/backend/dist/services/authService.d.ts.map +1 -1
- package/backend/dist/services/authService.js +15 -7
- package/backend/dist/services/authService.js.map +1 -1
- package/backend/dist/services/chatGenerationService.d.ts +32 -0
- package/backend/dist/services/chatGenerationService.d.ts.map +1 -0
- package/backend/dist/services/chatGenerationService.js +118 -0
- package/backend/dist/services/chatGenerationService.js.map +1 -0
- package/backend/dist/services/chatRequestService.d.ts +55 -0
- package/backend/dist/services/chatRequestService.d.ts.map +1 -0
- package/backend/dist/services/chatRequestService.js +103 -0
- package/backend/dist/services/chatRequestService.js.map +1 -0
- package/backend/dist/services/chatService.d.ts +0 -1
- package/backend/dist/services/chatService.d.ts.map +1 -1
- package/backend/dist/services/chatService.js +17 -113
- package/backend/dist/services/chatService.js.map +1 -1
- package/backend/dist/services/documentService.d.ts.map +1 -1
- package/backend/dist/services/documentService.js +23 -21
- package/backend/dist/services/documentService.js.map +1 -1
- package/backend/dist/services/embeddingService.d.ts +9 -0
- package/backend/dist/services/embeddingService.d.ts.map +1 -0
- package/backend/dist/services/embeddingService.js +155 -0
- package/backend/dist/services/embeddingService.js.map +1 -0
- package/backend/dist/services/encryptionService.d.ts.map +1 -1
- package/backend/dist/services/encryptionService.js +26 -24
- package/backend/dist/services/encryptionService.js.map +1 -1
- package/backend/dist/services/galleryService.d.ts.map +1 -1
- package/backend/dist/services/galleryService.js +8 -6
- package/backend/dist/services/galleryService.js.map +1 -1
- package/backend/dist/services/memoryService.d.ts +2 -13
- package/backend/dist/services/memoryService.d.ts.map +1 -1
- package/backend/dist/services/memoryService.js +45 -211
- package/backend/dist/services/memoryService.js.map +1 -1
- package/backend/dist/services/memoryUtils.d.ts +29 -0
- package/backend/dist/services/memoryUtils.d.ts.map +1 -0
- package/backend/dist/services/memoryUtils.js +165 -0
- package/backend/dist/services/memoryUtils.js.map +1 -0
- package/backend/dist/services/mutationEngineService.d.ts.map +1 -1
- package/backend/dist/services/mutationEngineService.js +5 -3
- package/backend/dist/services/mutationEngineService.js.map +1 -1
- package/backend/dist/services/ollamaService.d.ts.map +1 -1
- package/backend/dist/services/ollamaService.js +67 -34
- package/backend/dist/services/ollamaService.js.map +1 -1
- package/backend/dist/services/personaService.d.ts.map +1 -1
- package/backend/dist/services/personaService.js +6 -2
- package/backend/dist/services/personaService.js.map +1 -1
- package/backend/dist/services/pluginCapabilityRegistryService.d.ts +11 -0
- package/backend/dist/services/pluginCapabilityRegistryService.d.ts.map +1 -0
- package/backend/dist/services/pluginCapabilityRegistryService.js +70 -0
- package/backend/dist/services/pluginCapabilityRegistryService.js.map +1 -0
- package/backend/dist/services/pluginCredentialsService.d.ts.map +1 -1
- package/backend/dist/services/pluginCredentialsService.js +13 -11
- package/backend/dist/services/pluginCredentialsService.js.map +1 -1
- package/backend/dist/services/pluginEmbeddingService.d.ts +26 -0
- package/backend/dist/services/pluginEmbeddingService.d.ts.map +1 -0
- package/backend/dist/services/pluginEmbeddingService.js +159 -0
- package/backend/dist/services/pluginEmbeddingService.js.map +1 -0
- package/backend/dist/services/pluginImageGenerationService.d.ts +29 -0
- package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -0
- package/backend/dist/services/pluginImageGenerationService.js +402 -0
- package/backend/dist/services/pluginImageGenerationService.js.map +1 -0
- package/backend/dist/services/pluginService.d.ts +21 -17
- package/backend/dist/services/pluginService.d.ts.map +1 -1
- package/backend/dist/services/pluginService.js +135 -1356
- package/backend/dist/services/pluginService.js.map +1 -1
- package/backend/dist/services/pluginTTSService.d.ts +28 -0
- package/backend/dist/services/pluginTTSService.d.ts.map +1 -0
- package/backend/dist/services/pluginTTSService.js +286 -0
- package/backend/dist/services/pluginTTSService.js.map +1 -0
- package/backend/dist/services/pluginVariablesService.d.ts.map +1 -1
- package/backend/dist/services/pluginVariablesService.js +7 -5
- package/backend/dist/services/pluginVariablesService.js.map +1 -1
- package/backend/dist/services/preferencesService.d.ts +1 -0
- package/backend/dist/services/preferencesService.d.ts.map +1 -1
- package/backend/dist/services/preferencesService.js +21 -7
- package/backend/dist/services/preferencesService.js.map +1 -1
- package/backend/dist/services/simpleGitHubOAuth.d.ts.map +1 -1
- package/backend/dist/services/simpleGitHubOAuth.js +10 -8
- package/backend/dist/services/simpleGitHubOAuth.js.map +1 -1
- package/backend/dist/services/simpleHuggingFaceOAuth.d.ts.map +1 -1
- package/backend/dist/services/simpleHuggingFaceOAuth.js +11 -9
- package/backend/dist/services/simpleHuggingFaceOAuth.js.map +1 -1
- package/backend/dist/services/systemSettingsService.d.ts.map +1 -1
- package/backend/dist/services/systemSettingsService.js +4 -2
- package/backend/dist/services/systemSettingsService.js.map +1 -1
- package/backend/dist/services/titleGenerationService.d.ts +55 -0
- package/backend/dist/services/titleGenerationService.d.ts.map +1 -0
- package/backend/dist/services/titleGenerationService.js +121 -0
- package/backend/dist/services/titleGenerationService.js.map +1 -0
- package/backend/dist/services/turnstileService.d.ts +13 -0
- package/backend/dist/services/turnstileService.d.ts.map +1 -0
- package/backend/dist/services/turnstileService.js +79 -0
- package/backend/dist/services/turnstileService.js.map +1 -0
- package/backend/dist/storage.d.ts +2 -22
- package/backend/dist/storage.d.ts.map +1 -1
- package/backend/dist/storage.js +22 -170
- package/backend/dist/storage.js.map +1 -1
- package/backend/dist/storageMappers.d.ts +82 -0
- package/backend/dist/storageMappers.d.ts.map +1 -0
- package/backend/dist/storageMappers.js +85 -0
- package/backend/dist/storageMappers.js.map +1 -0
- package/backend/dist/test-encryption.js +20 -18
- package/backend/dist/test-encryption.js.map +1 -1
- package/backend/dist/types/index.d.ts +6 -0
- package/backend/dist/types/index.d.ts.map +1 -1
- package/backend/dist/types/index.js.map +1 -1
- package/backend/dist/utils/assistantBranching.d.ts +8 -0
- package/backend/dist/utils/assistantBranching.d.ts.map +1 -0
- package/backend/dist/utils/assistantBranching.js +30 -0
- package/backend/dist/utils/assistantBranching.js.map +1 -0
- package/backend/dist/utils/chatContext.d.ts +17 -0
- package/backend/dist/utils/chatContext.d.ts.map +1 -0
- package/backend/dist/utils/chatContext.js +82 -0
- package/backend/dist/utils/chatContext.js.map +1 -0
- package/backend/dist/utils/chatDocumentContext.d.ts +7 -0
- package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -0
- package/backend/dist/utils/chatDocumentContext.js +51 -0
- package/backend/dist/utils/chatDocumentContext.js.map +1 -0
- package/backend/dist/utils/logger.d.ts +12 -0
- package/backend/dist/utils/logger.d.ts.map +1 -0
- package/backend/dist/utils/logger.js +82 -0
- package/backend/dist/utils/logger.js.map +1 -0
- package/backend/dist/utils/ollamaLibrary.d.ts +17 -0
- package/backend/dist/utils/ollamaLibrary.d.ts.map +1 -0
- package/backend/dist/utils/ollamaLibrary.js +253 -0
- package/backend/dist/utils/ollamaLibrary.js.map +1 -0
- package/backend/dist/utils/ollamaStreaming.d.ts +19 -0
- package/backend/dist/utils/ollamaStreaming.d.ts.map +1 -0
- package/backend/dist/utils/ollamaStreaming.js +67 -0
- package/backend/dist/utils/ollamaStreaming.js.map +1 -0
- package/backend/dist/utils/packagePaths.d.ts +10 -0
- package/backend/dist/utils/packagePaths.d.ts.map +1 -0
- package/backend/dist/utils/packagePaths.js +81 -0
- package/backend/dist/utils/packagePaths.js.map +1 -0
- package/backend/dist/utils/pluginChatAdapter.d.ts +32 -0
- package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -0
- package/backend/dist/utils/pluginChatAdapter.js +308 -0
- package/backend/dist/utils/pluginChatAdapter.js.map +1 -0
- package/backend/dist/utils/pluginChatContext.d.ts +23 -0
- package/backend/dist/utils/pluginChatContext.d.ts.map +1 -0
- package/backend/dist/utils/pluginChatContext.js +77 -0
- package/backend/dist/utils/pluginChatContext.js.map +1 -0
- package/backend/dist/utils/pluginResponse.d.ts +6 -0
- package/backend/dist/utils/pluginResponse.d.ts.map +1 -0
- package/backend/dist/utils/pluginResponse.js +70 -0
- package/backend/dist/utils/pluginResponse.js.map +1 -0
- package/backend/dist/utils/pluginStreamAdapter.d.ts +16 -0
- package/backend/dist/utils/pluginStreamAdapter.d.ts.map +1 -0
- package/backend/dist/utils/pluginStreamAdapter.js +141 -0
- package/backend/dist/utils/pluginStreamAdapter.js.map +1 -0
- package/backend/dist/utils/pluginStreaming.d.ts +24 -0
- package/backend/dist/utils/pluginStreaming.d.ts.map +1 -0
- package/backend/dist/utils/pluginStreaming.js +106 -0
- package/backend/dist/utils/pluginStreaming.js.map +1 -0
- package/backend/dist/utils/pluginUpload.d.ts +8 -0
- package/backend/dist/utils/pluginUpload.d.ts.map +1 -0
- package/backend/dist/utils/pluginUpload.js +51 -0
- package/backend/dist/utils/pluginUpload.js.map +1 -0
- package/backend/dist/utils/pluginValidation.d.ts +10 -0
- package/backend/dist/utils/pluginValidation.d.ts.map +1 -0
- package/backend/dist/utils/pluginValidation.js +94 -0
- package/backend/dist/utils/pluginValidation.js.map +1 -0
- package/backend/dist/utils/pluginVariableValidation.d.ts +10 -0
- package/backend/dist/utils/pluginVariableValidation.d.ts.map +1 -0
- package/backend/dist/utils/pluginVariableValidation.js +80 -0
- package/backend/dist/utils/pluginVariableValidation.js.map +1 -0
- package/backend/dist/utils/websocketMessages.d.ts +26 -0
- package/backend/dist/utils/websocketMessages.d.ts.map +1 -0
- package/backend/dist/utils/websocketMessages.js +74 -0
- package/backend/dist/utils/websocketMessages.js.map +1 -0
- package/backend/dist/websocketServer.d.ts +3 -0
- package/backend/dist/websocketServer.d.ts.map +1 -0
- package/backend/dist/websocketServer.js +332 -0
- package/backend/dist/websocketServer.js.map +1 -0
- package/backend/package.json +55 -0
- package/frontend/dist/assets/index-BxHDVIti.css +1 -0
- package/frontend/dist/assets/index-CDA2f43l.js +5 -0
- package/frontend/dist/assets/markdown-math-CYlHEDRF.css +1 -0
- package/frontend/dist/index.html +12 -15
- package/frontend/dist/js/ArtifactContainer-CA523BOS.js +1 -0
- package/frontend/dist/js/ArtifactDemoPage-C0p2-ccP.js +200 -0
- package/frontend/dist/js/ArtifactSlideOutPanel-BBYuYyr_.js +1 -0
- package/frontend/dist/js/Button-BmZQkAPD.js +1 -0
- package/frontend/dist/js/ChatPage-QSOhIynr.js +16 -0
- package/frontend/dist/js/FirstTimeSetup-pi-S4VU-.js +1 -0
- package/frontend/dist/js/GalleryPage-BPXB-D4f.js +1 -0
- package/frontend/dist/js/ModelManagerModals-D3SZV7CU.js +1 -0
- package/frontend/dist/js/ModelsPage-Fmqc1Zxb.js +2 -0
- package/frontend/dist/js/OptimizedSyntaxHighlighter-DlI4omE2.js +1 -0
- package/frontend/dist/js/PersonasPage-BuLOCG1Y.js +13 -0
- package/frontend/dist/js/RichMessageContent-Darvuw8F.js +3 -0
- package/frontend/dist/js/SettingsModal-CQhAeaia.js +1 -0
- package/frontend/dist/js/UserManagementPage-yX9vOase.js +1 -0
- package/frontend/dist/js/api-d05r9G59.js +15 -0
- package/frontend/dist/js/ar-BIL8U_dv.js +2 -0
- package/frontend/dist/js/artifactHtml-CRdkOCnz.js +27 -0
- package/frontend/dist/js/bn-BWQoWPn6.js +2 -0
- package/frontend/dist/js/cs-DOD1vscE.js +2 -0
- package/frontend/dist/js/da-DYPzSzX4.js +2 -0
- package/frontend/dist/js/de-CHd4b2Xf.js +2 -0
- package/frontend/dist/js/demoMode-BhEpMVh5.js +1 -0
- package/frontend/dist/js/es-BmDO6uNe.js +2 -0
- package/frontend/dist/js/fr-DW24sUV7.js +2 -0
- package/frontend/dist/js/hi-BRDq_y1B.js +2 -0
- package/frontend/dist/js/id-I_F2nstV.js +2 -0
- package/frontend/dist/js/is-Boj7-J12.js +2 -0
- package/frontend/dist/js/it-CNeHl9Fh.js +2 -0
- package/frontend/dist/js/ja-DrVG6G8s.js +2 -0
- package/frontend/dist/js/ko-BtKRwlHW.js +2 -0
- package/frontend/dist/js/logger-1THq8-96.js +1 -0
- package/frontend/dist/js/markdown-core-CEpaJrKd.js +16 -0
- package/frontend/dist/js/markdown-math-DAmpvM9y.js +261 -0
- package/frontend/dist/js/ms-Dp9MigoU.js +2 -0
- package/frontend/dist/js/nl-Bk9pGoCF.js +2 -0
- package/frontend/dist/js/pl-seE8_lLS.js +2 -0
- package/frontend/dist/js/pt-BAeBtldI.js +2 -0
- package/frontend/dist/js/{react-vendor-BUzyfgnZ.js ā react-vendor-DtQrJICq.js} +4 -4
- package/frontend/dist/js/rolldown-runtime-QTnfLwEv.js +1 -0
- package/frontend/dist/js/ru-CkCpWFNb.js +2 -0
- package/frontend/dist/js/sv-BM7nqxmA.js +2 -0
- package/frontend/dist/js/syntax-highlight-B758xPeo.js +8 -0
- package/frontend/dist/js/th-vDIZbpa4.js +2 -0
- package/frontend/dist/js/tr-G2-XodhW.js +2 -0
- package/frontend/dist/js/ui-vendor-MCs1MDmN.js +177 -0
- package/frontend/dist/js/uk-DfVfZcBQ.js +2 -0
- package/frontend/dist/js/useBaseQuery-CrdrLcmR.js +1 -0
- package/frontend/dist/js/useQuery-DaEnYfWR.js +1 -0
- package/frontend/dist/js/utils-es0n-47H.js +1 -0
- package/frontend/dist/js/utils-vendor-B2ZBL57Y.js +9 -0
- package/frontend/dist/js/vi-Yp9yjb06.js +2 -0
- package/frontend/dist/js/zh-DtRb97we.js +2 -0
- package/package.json +16 -10
- package/plugins/llama-cpp.json +93 -0
- package/plugins/openclaw-agent.json +1 -1
- package/backend/dist/services/openclawSessionService.d.ts +0 -63
- package/backend/dist/services/openclawSessionService.d.ts.map +0 -1
- package/backend/dist/services/openclawSessionService.js +0 -307
- package/backend/dist/services/openclawSessionService.js.map +0 -1
- package/frontend/dist/assets/index-B1qNw5pA.js +0 -52
- package/frontend/dist/assets/index-DnG6512Q.css +0 -1
- package/frontend/dist/assets/ui-CsNe6lFV.css +0 -1
- package/frontend/dist/js/ArtifactContainer-Com7P5vK.js +0 -24
- package/frontend/dist/js/ArtifactDemoPage-Xd-Ragpf.js +0 -98
- package/frontend/dist/js/Button-D62PCbMW.js +0 -1
- package/frontend/dist/js/ChatPage-01JxVHRP.js +0 -15
- package/frontend/dist/js/GalleryPage-8nHGqKEu.js +0 -1
- package/frontend/dist/js/ModelsPage-C0MLVfwA.js +0 -1
- package/frontend/dist/js/OptimizedSyntaxHighlighter-DjnSqskO.js +0 -1
- package/frontend/dist/js/PersonasPage-D9YKnYz6.js +0 -13
- package/frontend/dist/js/UserManagementPage-b6NZdPTD.js +0 -1
- package/frontend/dist/js/api-B0pf9vdR.js +0 -1
- package/frontend/dist/js/api-BrzQvj_g.js +0 -3
- package/frontend/dist/js/appStore-k_e3JAM3.js +0 -2
- package/frontend/dist/js/authStore-BkobbhZS.js +0 -1
- package/frontend/dist/js/authStore-ge66S03D.js +0 -2
- package/frontend/dist/js/axios-CYrHjdt1.js +0 -1
- package/frontend/dist/js/demoMode-qyEGd_UM.js +0 -1
- package/frontend/dist/js/markdown-vendor-4lCPqxk5.js +0 -22
- package/frontend/dist/js/rolldown-runtime-DWy-cpC1.js +0 -1
- package/frontend/dist/js/ui-DuH1cD2f.js +0 -267
- package/frontend/dist/js/ui-vendor-BFOABH-A.js +0 -177
- package/frontend/dist/js/userService-BndgbS1C.js +0 -1
- package/frontend/dist/js/utils-vendor-MBozwMfr.js +0 -6
package/backend/dist/index.js
CHANGED
|
@@ -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
|
|
77
|
-
import
|
|
78
|
-
import
|
|
79
|
-
|
|
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' &&
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(``);
|
|
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
|
-
|
|
1289
|
-
|
|
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
|
-
|
|
394
|
+
logger.info('SSO configuration:');
|
|
1308
395
|
if (githubConfigured) {
|
|
1309
|
-
|
|
396
|
+
logger.info('GitHub OAuth configured and ready');
|
|
1310
397
|
}
|
|
1311
398
|
if (hfConfigured) {
|
|
1312
|
-
|
|
399
|
+
logger.info('Hugging Face OAuth configured and ready');
|
|
1313
400
|
}
|
|
1314
401
|
}
|
|
1315
402
|
else {
|
|
1316
|
-
|
|
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
|
-
|
|
408
|
+
logger.info('Ollama service is connected and ready');
|
|
1322
409
|
}
|
|
1323
410
|
else {
|
|
1324
|
-
|
|
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
|
-
|
|
417
|
+
logger.info('SIGTERM signal received: closing HTTP server');
|
|
1331
418
|
server.close(() => {
|
|
1332
|
-
|
|
419
|
+
logger.info('HTTP server closed');
|
|
1333
420
|
});
|
|
1334
421
|
});
|
|
1335
422
|
process.on('SIGINT', () => {
|
|
1336
|
-
|
|
423
|
+
logger.info('SIGINT signal received: closing HTTP server');
|
|
1337
424
|
server.close(() => {
|
|
1338
|
-
|
|
425
|
+
logger.info('HTTP server closed');
|
|
1339
426
|
});
|
|
1340
427
|
});
|
|
1341
428
|
export default app;
|