libre-webui 0.10.0 ā 0.12.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 +75 -61
- 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 +39 -908
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/middleware/auth.d.ts.map +1 -1
- package/backend/dist/middleware/auth.js +4 -2
- 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 -249
- 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/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/libreClaw.d.ts +3 -0
- package/backend/dist/routes/libreClaw.d.ts.map +1 -0
- package/backend/dist/routes/libreClaw.js +156 -0
- package/backend/dist/routes/libreClaw.js.map +1 -0
- package/backend/dist/routes/ollama.d.ts.map +1 -1
- package/backend/dist/routes/ollama.js +7 -247
- package/backend/dist/routes/ollama.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 +9 -5
- 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 +13 -27
- 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 +21 -19
- package/backend/dist/services/documentService.js.map +1 -1
- package/backend/dist/services/embeddingService.d.ts.map +1 -1
- package/backend/dist/services/embeddingService.js +3 -1
- package/backend/dist/services/embeddingService.js.map +1 -1
- 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/libreClawService.d.ts +105 -0
- package/backend/dist/services/libreClawService.d.ts.map +1 -0
- package/backend/dist/services/libreClawService.js +169 -0
- package/backend/dist/services/libreClawService.js.map +1 -0
- 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 +37 -198
- 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 +3 -1
- 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 +36 -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 +3 -1
- 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 +9 -18
- package/backend/dist/services/pluginService.d.ts.map +1 -1
- package/backend/dist/services/pluginService.js +118 -1469
- 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.map +1 -1
- package/backend/dist/services/preferencesService.js +9 -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.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/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 +9 -0
- package/backend/dist/utils/pluginValidation.d.ts.map +1 -0
- package/backend/dist/utils/pluginValidation.js +84 -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 +2 -2
- package/frontend/dist/assets/index-Dc8Q4dJZ.css +1 -0
- package/frontend/dist/assets/index-ONn9VIq7.js +5 -0
- package/frontend/dist/assets/markdown-math-CYlHEDRF.css +1 -0
- package/frontend/dist/index.html +11 -10
- package/frontend/dist/js/ArtifactContainer-CqKsiKz0.js +1 -0
- package/frontend/dist/js/ArtifactDemoPage-Dd5BjIu2.js +200 -0
- package/frontend/dist/js/ArtifactSlideOutPanel-Yc7ttIaY.js +1 -0
- package/frontend/dist/js/Button-BUjLpHmR.js +1 -0
- package/frontend/dist/js/ChatPage-Cpy7lQ0D.js +16 -0
- package/frontend/dist/js/FirstTimeSetup-DcGhm0H0.js +1 -0
- package/frontend/dist/js/GalleryPage-DNI2Evvf.js +1 -0
- package/frontend/dist/js/LibreClawPage-CmTiROsJ.js +1 -0
- package/frontend/dist/js/ModelManagerModals-CfvSeNsT.js +1 -0
- package/frontend/dist/js/ModelsPage-CeTGqx4q.js +2 -0
- package/frontend/dist/js/OptimizedSyntaxHighlighter-CNjLqj1Z.js +1 -0
- package/frontend/dist/js/PersonasPage-BUqu7gJ3.js +13 -0
- package/frontend/dist/js/RichMessageContent-B0JO8vkR.js +3 -0
- package/frontend/dist/js/SettingsModal-B5aJ5vfV.js +1 -0
- package/frontend/dist/js/UserManagementPage-BqIIWX2M.js +1 -0
- package/frontend/dist/js/api-CbmZzKBu.js +15 -0
- package/frontend/dist/js/ar-BAOdgn6O.js +2 -0
- package/frontend/dist/js/artifactHtml-B7Oy2e__.js +27 -0
- package/frontend/dist/js/bn-Bp2KS5mx.js +2 -0
- package/frontend/dist/js/cs-DOgJ0c5p.js +2 -0
- package/frontend/dist/js/da-BCKTkC35.js +2 -0
- package/frontend/dist/js/de-BXyECph5.js +2 -0
- package/frontend/dist/js/demoMode-BhEpMVh5.js +1 -0
- package/frontend/dist/js/es-Db4bDYIF.js +2 -0
- package/frontend/dist/js/fr-BPIkf6zH.js +2 -0
- package/frontend/dist/js/hi-Bm8D_Aux.js +2 -0
- package/frontend/dist/js/id-DiWaH74_.js +2 -0
- package/frontend/dist/js/is-DLgJywtZ.js +2 -0
- package/frontend/dist/js/it-lldrM8pE.js +2 -0
- package/frontend/dist/js/ja-Cfe9uZmQ.js +2 -0
- package/frontend/dist/js/ko-BhChO3MQ.js +2 -0
- package/frontend/dist/js/logger-BmpKNkls.js +1 -0
- package/frontend/dist/js/markdown-core-fkbHPd-g.js +16 -0
- package/frontend/dist/js/markdown-math-Ci4QOsf-.js +261 -0
- package/frontend/dist/js/ms-SWtmetXo.js +2 -0
- package/frontend/dist/js/nl-CUO6I3nO.js +2 -0
- package/frontend/dist/js/pl-BUAu96P6.js +2 -0
- package/frontend/dist/js/pt-DCHwoWiE.js +2 -0
- package/frontend/dist/js/{react-vendor-Dh6nfvM2.js ā react-vendor-Co1u1rWb.js} +2 -2
- package/frontend/dist/js/ru-Ciz053N7.js +2 -0
- package/frontend/dist/js/sv-D38ZHjv-.js +2 -0
- package/frontend/dist/js/syntax-highlight-D5wpTVgr.js +8 -0
- package/frontend/dist/js/th-D2cAucvn.js +2 -0
- package/frontend/dist/js/tr-Go355oDu.js +2 -0
- package/frontend/dist/js/ui-vendor-BJAeWL11.js +177 -0
- package/frontend/dist/js/uk-cdzjhgya.js +2 -0
- package/frontend/dist/js/useBaseQuery-6OJiOiyu.js +1 -0
- package/frontend/dist/js/useQuery-DfkI6VAv.js +1 -0
- package/frontend/dist/js/utils-BaPYjs_z.js +1 -0
- package/frontend/dist/js/utils-vendor-BX3ZwoIK.js +9 -0
- package/frontend/dist/js/vi-BSOpE423.js +2 -0
- package/frontend/dist/js/zh-BFh6sHZG.js +2 -0
- package/package.json +12 -8
- 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-cOuIFv6G.js +0 -314
- package/frontend/dist/assets/index-tuVjvNle.css +0 -1
- package/frontend/dist/js/ArtifactContainer-DXa2t8GS.js +0 -24
- package/frontend/dist/js/ArtifactDemoPage-Dms7P2gI.js +0 -98
- package/frontend/dist/js/ChatPage-C6hOVIAS.js +0 -15
- package/frontend/dist/js/GalleryPage-CGKPECJV.js +0 -1
- package/frontend/dist/js/ModelsPage-CNB-c-JM.js +0 -1
- package/frontend/dist/js/PersonasPage-hDiS0Rvl.js +0 -13
- package/frontend/dist/js/UserManagementPage-1H9kcHeR.js +0 -1
- package/frontend/dist/js/api-MQ7bONaU.js +0 -3
- package/frontend/dist/js/appStore-BpzQfKOd.js +0 -2
- package/frontend/dist/js/authStore-Cn_WS6Kb.js +0 -2
- package/frontend/dist/js/demoMode-PKHbba1m.js +0 -1
- package/frontend/dist/js/markdown-vendor-DLzJCMRQ.js +0 -22
- package/frontend/dist/js/ui-vendor-ClcVY3m-.js +0 -177
- package/frontend/dist/js/utils-vendor-BCQRX-0Q.js +0 -9
- package/plugins/openclaw-agent.json +0 -69
package/backend/dist/index.js
CHANGED
|
@@ -38,7 +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
41
|
import { errorHandler, notFoundHandler, requestLogger, } from './middleware/index.js';
|
|
43
42
|
import { optionalAuth } from './middleware/auth.js';
|
|
44
43
|
import ollamaRoutes from './routes/ollama.js';
|
|
@@ -53,19 +52,16 @@ import ttsRoutes from './routes/tts.js';
|
|
|
53
52
|
import imageGenRoutes from './routes/imageGen.js';
|
|
54
53
|
import embeddingsRoutes from './routes/embeddings.js';
|
|
55
54
|
import huggingfaceHubRoutes from './routes/huggingfaceHub.js';
|
|
55
|
+
import libreClawRoutes from './routes/libreClaw.js';
|
|
56
56
|
import ollamaService from './services/ollamaService.js';
|
|
57
|
-
import chatService from './services/chatService.js';
|
|
58
57
|
import { GitHubOAuthService } from './services/simpleGitHubOAuth.js';
|
|
59
58
|
import { HuggingFaceOAuthService } from './services/simpleHuggingFaceOAuth.js';
|
|
60
|
-
import pluginService from './services/pluginService.js';
|
|
61
|
-
import preferencesService from './services/preferencesService.js';
|
|
62
|
-
import documentService from './services/documentService.js';
|
|
63
|
-
import openclawSessionService, { extractTextFromMessage, } from './services/openclawSessionService.js';
|
|
64
|
-
import { mergeGenerationOptions } from './utils/generationUtils.js';
|
|
65
|
-
import { verifyToken } from './utils/jwt.js';
|
|
66
59
|
import { loadAppPackage, resolveFrontendDist } from './utils/packagePaths.js';
|
|
60
|
+
import { registerWebSocketServer } from './websocketServer.js';
|
|
61
|
+
import { createLogger } from './utils/logger.js';
|
|
67
62
|
const pkg = loadAppPackage(import.meta.url);
|
|
68
63
|
const app = express();
|
|
64
|
+
const logger = createLogger('server');
|
|
69
65
|
// Trust proxy setting for running behind reverse proxies (Nginx, Caddy, etc.)
|
|
70
66
|
// Set TRUST_PROXY=1 or TRUST_PROXY=loopback or TRUST_PROXY=uniquelocal etc.
|
|
71
67
|
const trustProxy = process.env.TRUST_PROXY;
|
|
@@ -83,6 +79,8 @@ if (trustProxy) {
|
|
|
83
79
|
}
|
|
84
80
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
85
81
|
const port = process.env.PORT || (isProduction ? 8080 : 3001);
|
|
82
|
+
const hasTurnstile = Boolean(process.env.TURNSTILE_SITE_KEY?.trim()) &&
|
|
83
|
+
Boolean(process.env.TURNSTILE_SECRET_KEY?.trim());
|
|
86
84
|
const corsOrigins = process.env.CORS_ORIGIN?.split(',') || [
|
|
87
85
|
'http://localhost:5173',
|
|
88
86
|
'http://localhost:3000',
|
|
@@ -109,7 +107,7 @@ const corsConfig = {
|
|
|
109
107
|
const allowNetworkAccess = process.env.NODE_ENV !== 'production' ||
|
|
110
108
|
process.env.DOCKER_ENV === 'true';
|
|
111
109
|
const isNetworkOrigin = origin &&
|
|
112
|
-
/^https?:\/\/(?:192\.168\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|127\.|localhost)/.test(origin);
|
|
110
|
+
/^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);
|
|
113
111
|
if (allowNetworkAccess && isNetworkOrigin) {
|
|
114
112
|
callback(null, true);
|
|
115
113
|
}
|
|
@@ -122,7 +120,9 @@ const corsConfig = {
|
|
|
122
120
|
// Security middleware
|
|
123
121
|
app.use(helmet({
|
|
124
122
|
// COEP - disable in Docker/development to avoid proxy issues
|
|
125
|
-
crossOriginEmbedderPolicy: process.env.NODE_ENV === 'production' &&
|
|
123
|
+
crossOriginEmbedderPolicy: process.env.NODE_ENV === 'production' &&
|
|
124
|
+
!process.env.DOCKER_ENV &&
|
|
125
|
+
!hasTurnstile
|
|
126
126
|
? true
|
|
127
127
|
: false,
|
|
128
128
|
// Content Security Policy - Docker-aware configuration
|
|
@@ -131,6 +131,7 @@ app.use(helmet({
|
|
|
131
131
|
defaultSrc: ["'self'"],
|
|
132
132
|
scriptSrc: [
|
|
133
133
|
"'self'",
|
|
134
|
+
'https://challenges.cloudflare.com',
|
|
134
135
|
...(process.env.NODE_ENV === 'production'
|
|
135
136
|
? [] // Strict in production
|
|
136
137
|
: ["'unsafe-inline'", "'unsafe-eval'"]), // Allow for dev tools
|
|
@@ -164,6 +165,7 @@ app.use(helmet({
|
|
|
164
165
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
|
|
165
166
|
mediaSrc: ["'self'", 'data:', 'blob:'],
|
|
166
167
|
objectSrc: ["'none'"],
|
|
168
|
+
frameSrc: ["'self'", 'https://challenges.cloudflare.com'],
|
|
167
169
|
frameAncestors: ["'self'"],
|
|
168
170
|
formAction: ["'self'"],
|
|
169
171
|
upgradeInsecureRequests: process.env.NODE_ENV === 'production' && !process.env.DOCKER_ENV
|
|
@@ -315,6 +317,17 @@ const imageGenRateLimiter = rateLimit({
|
|
|
315
317
|
standardHeaders: true,
|
|
316
318
|
legacyHeaders: false,
|
|
317
319
|
});
|
|
320
|
+
// Rate limiter for Libre Claw agent routes
|
|
321
|
+
const libreClawRateLimiter = rateLimit({
|
|
322
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
323
|
+
max: 500, // agent dashboards poll run/event state while active
|
|
324
|
+
message: {
|
|
325
|
+
success: false,
|
|
326
|
+
error: 'Too many Libre Claw requests from this IP, please try again later.',
|
|
327
|
+
},
|
|
328
|
+
standardHeaders: true,
|
|
329
|
+
legacyHeaders: false,
|
|
330
|
+
});
|
|
318
331
|
// API routes
|
|
319
332
|
app.use('/api/auth', authRateLimiter, optionalAuth, authRoutes);
|
|
320
333
|
app.use('/api/users', usersRateLimiter, optionalAuth, usersRoutes);
|
|
@@ -328,13 +341,14 @@ app.use('/api/personas', personasRateLimiter, optionalAuth, personaRoutes);
|
|
|
328
341
|
app.use('/api/tts', ttsRateLimiter, optionalAuth, ttsRoutes);
|
|
329
342
|
app.use('/api/image-gen', imageGenRateLimiter, optionalAuth, imageGenRoutes);
|
|
330
343
|
app.use('/api/huggingface-hub', huggingfaceHubRoutes);
|
|
344
|
+
app.use('/api/libre-claw', libreClawRateLimiter, libreClawRoutes);
|
|
331
345
|
// Serve frontend static files in production (for npx libre-webui)
|
|
332
346
|
if (process.env.NODE_ENV === 'production' ||
|
|
333
347
|
process.env.SERVE_FRONTEND === 'true') {
|
|
334
348
|
const pathModule = await import('path');
|
|
335
349
|
const frontendPath = resolveFrontendDist(import.meta.url);
|
|
336
350
|
if (frontendPath) {
|
|
337
|
-
|
|
351
|
+
logger.info(`Serving frontend from: ${frontendPath}`);
|
|
338
352
|
// Rate limiter for static files
|
|
339
353
|
const staticRateLimiter = rateLimit({
|
|
340
354
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
@@ -359,7 +373,7 @@ if (process.env.NODE_ENV === 'production' ||
|
|
|
359
373
|
});
|
|
360
374
|
}
|
|
361
375
|
else {
|
|
362
|
-
|
|
376
|
+
logger.warn('Frontend build not found. Run `npm run build:frontend` first.');
|
|
363
377
|
}
|
|
364
378
|
}
|
|
365
379
|
// Error handling
|
|
@@ -367,895 +381,12 @@ app.use(notFoundHandler);
|
|
|
367
381
|
app.use(errorHandler);
|
|
368
382
|
// Create HTTP server
|
|
369
383
|
const server = createServer(app);
|
|
370
|
-
|
|
371
|
-
const wss = new WebSocketServer({
|
|
372
|
-
server,
|
|
373
|
-
path: '/ws',
|
|
374
|
-
});
|
|
375
|
-
wss.on('connection', (ws, req) => {
|
|
376
|
-
console.log('WebSocket client connected');
|
|
377
|
-
// Extract and verify auth token from query parameters
|
|
378
|
-
let userId = 'default';
|
|
379
|
-
try {
|
|
380
|
-
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
381
|
-
const token = url.searchParams.get('token');
|
|
382
|
-
if (token) {
|
|
383
|
-
// Verify JWT token using the same logic as the auth middleware
|
|
384
|
-
const decoded = verifyToken(token);
|
|
385
|
-
userId = decoded.userId;
|
|
386
|
-
console.log('WebSocket authenticated for user:', userId);
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
console.log('WebSocket connection without auth token, using default user');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
// An expired/invalid token here is expected (e.g. a stale browser token);
|
|
394
|
-
// fall back to the default user without dumping a stack trace.
|
|
395
|
-
console.warn('WebSocket auth failed, using default user:', error instanceof Error ? error.message : error);
|
|
396
|
-
// Continue with default user for backward compatibility
|
|
397
|
-
}
|
|
398
|
-
ws.on('message', async (data) => {
|
|
399
|
-
try {
|
|
400
|
-
const message = JSON.parse(data.toString());
|
|
401
|
-
if (message.type === 'chat_stream') {
|
|
402
|
-
const { sessionId, content, images, format, options, assistantMessageId, regenerate, originalMessageId, isPrivate, model: privateModel, messageHistory, } = message.data;
|
|
403
|
-
console.log('Backend: Received chat_stream for session:', sessionId, 'with images:', !!images, 'format:', !!format, 'regenerate:', !!regenerate, 'originalMessageId:', originalMessageId, 'isPrivate:', !!isPrivate);
|
|
404
|
-
// For private sessions, skip database operations
|
|
405
|
-
let session;
|
|
406
|
-
if (isPrivate) {
|
|
407
|
-
// Create a temporary in-memory session object for private mode
|
|
408
|
-
session = {
|
|
409
|
-
id: sessionId,
|
|
410
|
-
model: privateModel,
|
|
411
|
-
messages: [],
|
|
412
|
-
title: 'Private Chat',
|
|
413
|
-
createdAt: Date.now(),
|
|
414
|
-
updatedAt: Date.now(),
|
|
415
|
-
userId: userId,
|
|
416
|
-
isPrivate: true,
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
// Get session with user authentication
|
|
421
|
-
session = chatService.getSession(sessionId, userId);
|
|
422
|
-
if (!session) {
|
|
423
|
-
console.log('Backend: Session not found:', sessionId, 'for user:', userId);
|
|
424
|
-
ws.send(JSON.stringify({
|
|
425
|
-
type: 'error',
|
|
426
|
-
data: {
|
|
427
|
-
error: 'Session not found',
|
|
428
|
-
code: 'SESSION_NOT_FOUND',
|
|
429
|
-
message: 'The requested session does not exist or does not belong to the current user. Please create a new session.',
|
|
430
|
-
sessionId: sessionId,
|
|
431
|
-
userId: userId,
|
|
432
|
-
},
|
|
433
|
-
}));
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
// Add user message with images if provided (skip for regenerations and private sessions)
|
|
438
|
-
let userMessage;
|
|
439
|
-
if (!regenerate && !isPrivate) {
|
|
440
|
-
userMessage = chatService.addMessage(sessionId, {
|
|
441
|
-
role: 'user',
|
|
442
|
-
content,
|
|
443
|
-
images: images || undefined,
|
|
444
|
-
}, userId);
|
|
445
|
-
if (!userMessage) {
|
|
446
|
-
ws.send(JSON.stringify({
|
|
447
|
-
type: 'error',
|
|
448
|
-
data: { error: 'Failed to add user message' },
|
|
449
|
-
}));
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
// Send user message confirmation
|
|
453
|
-
ws.send(JSON.stringify({
|
|
454
|
-
type: 'user_message',
|
|
455
|
-
data: userMessage,
|
|
456
|
-
}));
|
|
457
|
-
}
|
|
458
|
-
else if (!regenerate && isPrivate) {
|
|
459
|
-
// For private sessions, just send a confirmation without saving
|
|
460
|
-
ws.send(JSON.stringify({
|
|
461
|
-
type: 'user_message',
|
|
462
|
-
data: {
|
|
463
|
-
id: `private-msg-${Date.now()}`,
|
|
464
|
-
role: 'user',
|
|
465
|
-
content,
|
|
466
|
-
images: images || undefined,
|
|
467
|
-
timestamp: Date.now(),
|
|
468
|
-
},
|
|
469
|
-
}));
|
|
470
|
-
}
|
|
471
|
-
// RAG: Get relevant document context for the user's query
|
|
472
|
-
const relevantContext = await documentService.getRelevantContext(content, sessionId);
|
|
473
|
-
let enhancedContent = content;
|
|
474
|
-
if (relevantContext.length > 0) {
|
|
475
|
-
console.log(`Found ${relevantContext.length} relevant document chunks for query`);
|
|
476
|
-
// Inject document context into the user message
|
|
477
|
-
const contextString = relevantContext.join('\n\n---\n\n');
|
|
478
|
-
enhancedContent = `Context from uploaded documents:\n\n${contextString}\n\n---\n\nUser question: ${content}`;
|
|
479
|
-
// Update the user message with enhanced content that includes document context
|
|
480
|
-
// We'll create a new message with the enhanced content for the AI model
|
|
481
|
-
console.log('Enhanced user message with document context');
|
|
482
|
-
}
|
|
483
|
-
const contextMessages = isPrivate
|
|
484
|
-
? (messageHistory || []).concat([
|
|
485
|
-
{ role: 'user', content, images: images || undefined },
|
|
486
|
-
])
|
|
487
|
-
: chatService.getMessagesForContext(sessionId);
|
|
488
|
-
// Convert our messages to Ollama format
|
|
489
|
-
const ollamaMessages = contextMessages.map((msg, index) => {
|
|
490
|
-
const ollamaMessage = {
|
|
491
|
-
role: msg.role,
|
|
492
|
-
content: msg.content,
|
|
493
|
-
};
|
|
494
|
-
// Use enhanced content for the last user message if we have document context
|
|
495
|
-
if (msg.role === 'user' &&
|
|
496
|
-
index === contextMessages.length - 1 &&
|
|
497
|
-
relevantContext.length > 0) {
|
|
498
|
-
ollamaMessage.content = enhancedContent;
|
|
499
|
-
}
|
|
500
|
-
// Process images: strip data URL prefix if present
|
|
501
|
-
if (msg.images && msg.images.length > 0) {
|
|
502
|
-
ollamaMessage.images = msg.images.map((img) => {
|
|
503
|
-
// Strip data URL prefix if present (e.g., "data:image/png;base64,")
|
|
504
|
-
if (typeof img === 'string' && img.includes(',')) {
|
|
505
|
-
const base64Index = img.indexOf(',');
|
|
506
|
-
if (base64Index !== -1) {
|
|
507
|
-
return img.substring(base64Index + 1);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return img;
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
return ollamaMessage;
|
|
514
|
-
});
|
|
515
|
-
let assistantContent = '';
|
|
516
|
-
let finalStatistics = undefined;
|
|
517
|
-
console.log('Backend: Using assistantMessageId:', assistantMessageId);
|
|
518
|
-
// Resolve the actual model name (handles persona IDs)
|
|
519
|
-
let actualModelName = session.model;
|
|
520
|
-
if (session.model.startsWith('persona:')) {
|
|
521
|
-
try {
|
|
522
|
-
const personaId = session.model.replace('persona:', '');
|
|
523
|
-
console.log(`[WebSocket] DEBUG: Resolving persona ${personaId} for user ${userId}`);
|
|
524
|
-
const { personaService } = await import('./services/personaService.js');
|
|
525
|
-
// Try to get persona for the current user first, then fallback to 'default'
|
|
526
|
-
let persona = await personaService.getPersonaById(personaId, userId);
|
|
527
|
-
if (!persona && userId !== 'default') {
|
|
528
|
-
console.log(`[WebSocket] DEBUG: Persona not found for user ${userId}, trying default user`);
|
|
529
|
-
persona = await personaService.getPersonaById(personaId, 'default');
|
|
530
|
-
}
|
|
531
|
-
console.log(`[WebSocket] DEBUG: Persona lookup result:`, persona
|
|
532
|
-
? `Found persona with model: ${persona.model}`
|
|
533
|
-
: 'Persona not found');
|
|
534
|
-
if (persona && persona.model) {
|
|
535
|
-
actualModelName = persona.model;
|
|
536
|
-
console.log(`[WebSocket] Resolved persona ${personaId} to model: ${actualModelName}`);
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
console.warn(`[WebSocket] Persona ${personaId} not found, using original model: ${session.model}`);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
console.error(`[WebSocket] Error resolving persona model:`, error);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
// Check if there's an active plugin for this model
|
|
547
|
-
console.log(`[WebSocket] Looking for plugin for model: ${actualModelName}`);
|
|
548
|
-
const activePlugin = pluginService.getActivePluginForModel(actualModelName);
|
|
549
|
-
console.log(`[WebSocket] Found plugin:`, activePlugin ? activePlugin.id : 'none');
|
|
550
|
-
if (activePlugin) {
|
|
551
|
-
console.log(`[WebSocket] Using plugin ${activePlugin.id} for model ${actualModelName}`);
|
|
552
|
-
// Load plugin variables early ā needed for session mode check
|
|
553
|
-
const pluginVars = pluginService.getPluginVariables(activePlugin);
|
|
554
|
-
// ---------------------------------------------------------------
|
|
555
|
-
// OpenClaw Session Mode ā route through WebSocket gateway
|
|
556
|
-
// ---------------------------------------------------------------
|
|
557
|
-
// OpenClaw session routing now handled via x-openclaw-session-key HTTP header
|
|
558
|
-
// in pluginService ā no WebSocket needed
|
|
559
|
-
const isOpenClawSession = false; // Disabled: WS approach replaced by HTTP header
|
|
560
|
-
if (isOpenClawSession) {
|
|
561
|
-
console.log('[WebSocket] OpenClaw session mode: routing through gateway WS');
|
|
562
|
-
try {
|
|
563
|
-
// Ensure the gateway WS connection is established
|
|
564
|
-
const endpoint = pluginVars.endpoint ||
|
|
565
|
-
activePlugin.endpoint ||
|
|
566
|
-
'http://127.0.0.1:18789/v1/chat/completions';
|
|
567
|
-
const apiKey = pluginService.getApiKey(activePlugin) || '';
|
|
568
|
-
const ocSessionKey = pluginVars.session_key || 'main';
|
|
569
|
-
if (!openclawSessionService.isConnected) {
|
|
570
|
-
openclawSessionService.connect({
|
|
571
|
-
gatewayUrl: endpoint,
|
|
572
|
-
token: apiKey,
|
|
573
|
-
sessionKey: ocSessionKey,
|
|
574
|
-
});
|
|
575
|
-
// Wait a bit for connection
|
|
576
|
-
await new Promise(resolve => {
|
|
577
|
-
const maxWait = 8000;
|
|
578
|
-
const start = Date.now();
|
|
579
|
-
const check = () => {
|
|
580
|
-
if (openclawSessionService.isConnected) {
|
|
581
|
-
resolve();
|
|
582
|
-
}
|
|
583
|
-
else if (Date.now() - start > maxWait) {
|
|
584
|
-
resolve(); // proceed anyway, will error on send
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
setTimeout(check, 200);
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
check();
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
if (!openclawSessionService.isConnected) {
|
|
594
|
-
throw new Error('Failed to connect to OpenClaw gateway WebSocket');
|
|
595
|
-
}
|
|
596
|
-
// Build the message to send
|
|
597
|
-
const messageText = userMessage?.content || content;
|
|
598
|
-
// Set up event listener for this run
|
|
599
|
-
let totalContent = '';
|
|
600
|
-
let currentRunId = null;
|
|
601
|
-
let runDone = false;
|
|
602
|
-
const toolCalls = [];
|
|
603
|
-
const cleanup = openclawSessionService.subscribe((type, data) => {
|
|
604
|
-
if (runDone)
|
|
605
|
-
return;
|
|
606
|
-
if (type === 'chat') {
|
|
607
|
-
const chatEvent = data;
|
|
608
|
-
if (currentRunId &&
|
|
609
|
-
chatEvent.runId &&
|
|
610
|
-
chatEvent.runId !== currentRunId)
|
|
611
|
-
return;
|
|
612
|
-
if (chatEvent.state === 'delta') {
|
|
613
|
-
const text = extractTextFromMessage(chatEvent.message);
|
|
614
|
-
if (typeof text === 'string' &&
|
|
615
|
-
text.length > totalContent.length) {
|
|
616
|
-
// Send incremental delta
|
|
617
|
-
const newContent = text.slice(totalContent.length);
|
|
618
|
-
totalContent = text;
|
|
619
|
-
try {
|
|
620
|
-
ws.send(JSON.stringify({
|
|
621
|
-
type: 'assistant_chunk',
|
|
622
|
-
data: {
|
|
623
|
-
content: newContent,
|
|
624
|
-
total: totalContent,
|
|
625
|
-
done: false,
|
|
626
|
-
messageId: assistantMessageId,
|
|
627
|
-
},
|
|
628
|
-
}));
|
|
629
|
-
}
|
|
630
|
-
catch {
|
|
631
|
-
/* ws closed */
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
else if (chatEvent.state === 'final' ||
|
|
636
|
-
chatEvent.state === 'aborted' ||
|
|
637
|
-
chatEvent.state === 'error') {
|
|
638
|
-
runDone = true;
|
|
639
|
-
if (chatEvent.state === 'error') {
|
|
640
|
-
const errMsg = chatEvent.errorMessage || 'Agent error';
|
|
641
|
-
if (!totalContent)
|
|
642
|
-
totalContent = `Error: ${errMsg}`;
|
|
643
|
-
}
|
|
644
|
-
// Append tool call summaries
|
|
645
|
-
if (toolCalls.length > 0) {
|
|
646
|
-
let toolContent = '\n\n---\n**š§ Tools Used:**\n';
|
|
647
|
-
for (const tc of toolCalls) {
|
|
648
|
-
const statusIcon = tc.phase === 'result' ? 'ā
' : 'ā³';
|
|
649
|
-
toolContent += `\n${statusIcon} **${tc.name}**`;
|
|
650
|
-
if (tc.result) {
|
|
651
|
-
const resultStr = typeof tc.result === 'string'
|
|
652
|
-
? tc.result
|
|
653
|
-
: JSON.stringify(tc.result);
|
|
654
|
-
if (resultStr.length <= 500) {
|
|
655
|
-
toolContent += `\n<details><summary>Result</summary>\n\n\`\`\`\n${resultStr}\n\`\`\`\n</details>\n`;
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
toolContent += `\n<details><summary>Result (${resultStr.length} chars)</summary>\n\n\`\`\`\n${resultStr.slice(0, 500)}ā¦\n\`\`\`\n</details>\n`;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
totalContent += toolContent;
|
|
663
|
-
}
|
|
664
|
-
// Send final chunk
|
|
665
|
-
try {
|
|
666
|
-
ws.send(JSON.stringify({
|
|
667
|
-
type: 'assistant_chunk',
|
|
668
|
-
data: {
|
|
669
|
-
content: '',
|
|
670
|
-
total: totalContent,
|
|
671
|
-
done: true,
|
|
672
|
-
messageId: assistantMessageId,
|
|
673
|
-
},
|
|
674
|
-
}));
|
|
675
|
-
}
|
|
676
|
-
catch {
|
|
677
|
-
/* ws closed */
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
else if (type === 'tool') {
|
|
682
|
-
const toolEvent = data;
|
|
683
|
-
// Track tool calls
|
|
684
|
-
const existing = toolCalls.find(t => t.id === toolEvent.toolCallId);
|
|
685
|
-
if (existing) {
|
|
686
|
-
existing.phase = toolEvent.phase;
|
|
687
|
-
if (toolEvent.result)
|
|
688
|
-
existing.result =
|
|
689
|
-
typeof toolEvent.result === 'string'
|
|
690
|
-
? toolEvent.result
|
|
691
|
-
: JSON.stringify(toolEvent.result);
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
toolCalls.push({
|
|
695
|
-
id: toolEvent.toolCallId,
|
|
696
|
-
name: toolEvent.name,
|
|
697
|
-
phase: toolEvent.phase,
|
|
698
|
-
args: toolEvent.args
|
|
699
|
-
? JSON.stringify(toolEvent.args)
|
|
700
|
-
: undefined,
|
|
701
|
-
result: toolEvent.result
|
|
702
|
-
? typeof toolEvent.result === 'string'
|
|
703
|
-
? toolEvent.result
|
|
704
|
-
: JSON.stringify(toolEvent.result)
|
|
705
|
-
: undefined,
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
// Send tool status to frontend
|
|
709
|
-
// Send tool_status event for the activity indicator
|
|
710
|
-
try {
|
|
711
|
-
ws.send(JSON.stringify({
|
|
712
|
-
type: 'tool_status',
|
|
713
|
-
data: {
|
|
714
|
-
toolCallId: toolEvent.toolCallId,
|
|
715
|
-
name: toolEvent.name,
|
|
716
|
-
phase: toolEvent.phase === 'start'
|
|
717
|
-
? 'running'
|
|
718
|
-
: toolEvent.phase,
|
|
719
|
-
},
|
|
720
|
-
}));
|
|
721
|
-
}
|
|
722
|
-
catch {
|
|
723
|
-
/* ws closed */
|
|
724
|
-
}
|
|
725
|
-
try {
|
|
726
|
-
const toolStatusMsg = toolEvent.phase === 'start'
|
|
727
|
-
? `\n\nš§ *Using tool: ${toolEvent.name}ā¦*\n`
|
|
728
|
-
: '';
|
|
729
|
-
if (toolStatusMsg) {
|
|
730
|
-
totalContent += toolStatusMsg;
|
|
731
|
-
ws.send(JSON.stringify({
|
|
732
|
-
type: 'assistant_chunk',
|
|
733
|
-
data: {
|
|
734
|
-
content: toolStatusMsg,
|
|
735
|
-
total: totalContent,
|
|
736
|
-
done: false,
|
|
737
|
-
messageId: assistantMessageId,
|
|
738
|
-
},
|
|
739
|
-
}));
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
catch {
|
|
743
|
-
/* ws closed */
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
// Send the message
|
|
748
|
-
const result = await openclawSessionService.sendMessage(messageText, ocSessionKey);
|
|
749
|
-
currentRunId = result.runId;
|
|
750
|
-
// Wait for completion (max 5 minutes)
|
|
751
|
-
await new Promise(resolve => {
|
|
752
|
-
const timeout = setTimeout(() => {
|
|
753
|
-
runDone = true;
|
|
754
|
-
resolve();
|
|
755
|
-
}, 300000);
|
|
756
|
-
const checkDone = setInterval(() => {
|
|
757
|
-
if (runDone) {
|
|
758
|
-
clearInterval(checkDone);
|
|
759
|
-
clearTimeout(timeout);
|
|
760
|
-
resolve();
|
|
761
|
-
}
|
|
762
|
-
}, 100);
|
|
763
|
-
});
|
|
764
|
-
// Clean up listener
|
|
765
|
-
cleanup();
|
|
766
|
-
// Save the assistant message
|
|
767
|
-
assistantContent = totalContent;
|
|
768
|
-
}
|
|
769
|
-
catch (error) {
|
|
770
|
-
console.error('[WebSocket] OpenClaw session error:', error);
|
|
771
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
772
|
-
assistantContent = `Error: ${errorMsg}`;
|
|
773
|
-
ws.send(JSON.stringify({
|
|
774
|
-
type: 'assistant_chunk',
|
|
775
|
-
data: {
|
|
776
|
-
content: assistantContent,
|
|
777
|
-
total: assistantContent,
|
|
778
|
-
done: true,
|
|
779
|
-
messageId: assistantMessageId,
|
|
780
|
-
},
|
|
781
|
-
}));
|
|
782
|
-
}
|
|
783
|
-
// Save assistant message from session mode
|
|
784
|
-
if (assistantContent && assistantMessageId) {
|
|
785
|
-
if (isPrivate) {
|
|
786
|
-
ws.send(JSON.stringify({
|
|
787
|
-
type: 'assistant_complete',
|
|
788
|
-
data: {
|
|
789
|
-
id: assistantMessageId,
|
|
790
|
-
role: 'assistant',
|
|
791
|
-
content: assistantContent,
|
|
792
|
-
model: session.model,
|
|
793
|
-
timestamp: Date.now(),
|
|
794
|
-
},
|
|
795
|
-
}));
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
const assistantMessage = chatService.addMessage(sessionId, {
|
|
799
|
-
role: 'assistant',
|
|
800
|
-
content: assistantContent,
|
|
801
|
-
model: session.model,
|
|
802
|
-
id: assistantMessageId,
|
|
803
|
-
}, userId);
|
|
804
|
-
ws.send(JSON.stringify({
|
|
805
|
-
type: 'assistant_complete',
|
|
806
|
-
data: assistantMessage,
|
|
807
|
-
}));
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return; // Exit early ā handled via OpenClaw session
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
// ---------------------------------------------------------------
|
|
814
|
-
// Standard plugin path (stateless HTTP completions)
|
|
815
|
-
// ---------------------------------------------------------------
|
|
816
|
-
try {
|
|
817
|
-
// Get user's preferred generation options
|
|
818
|
-
const userGenerationOptions = preferencesService.getGenerationOptions();
|
|
819
|
-
// Merge user preferences with request options
|
|
820
|
-
const mergedOptions = mergeGenerationOptions(userGenerationOptions, options);
|
|
821
|
-
// Get messages for context
|
|
822
|
-
const contextMessages = chatService.getMessagesForContext(sessionId);
|
|
823
|
-
// For regenerations, the user message is already in context; for new messages, we need to add it
|
|
824
|
-
let messagesForPlugin = regenerate
|
|
825
|
-
? contextMessages
|
|
826
|
-
: contextMessages.concat([userMessage]);
|
|
827
|
-
const systemPromptPrefix = pluginVars.system_prompt_prefix || '';
|
|
828
|
-
const userName = pluginVars.user_name || '';
|
|
829
|
-
// Prepend identity system message if configured
|
|
830
|
-
if (systemPromptPrefix || userName) {
|
|
831
|
-
let identityMsg = systemPromptPrefix;
|
|
832
|
-
if (userName) {
|
|
833
|
-
identityMsg = identityMsg
|
|
834
|
-
? `${identityMsg}\n\nThe user's name is: ${userName}`
|
|
835
|
-
: `The user's name is: ${userName}`;
|
|
836
|
-
}
|
|
837
|
-
messagesForPlugin = [
|
|
838
|
-
{
|
|
839
|
-
id: 'system-identity',
|
|
840
|
-
role: 'system',
|
|
841
|
-
content: identityMsg,
|
|
842
|
-
timestamp: Date.now(),
|
|
843
|
-
},
|
|
844
|
-
...messagesForPlugin,
|
|
845
|
-
];
|
|
846
|
-
}
|
|
847
|
-
const shouldStream = pluginVars.stream ?? false;
|
|
848
|
-
if (shouldStream) {
|
|
849
|
-
// Real SSE streaming from plugin
|
|
850
|
-
let totalContent = '';
|
|
851
|
-
const toolCalls = [];
|
|
852
|
-
// Tool activity tracking ā single indicator, reused ID
|
|
853
|
-
let pauseTimer = null;
|
|
854
|
-
const PAUSE_TOOL_ID = 'tool-activity';
|
|
855
|
-
let toolActivitySent = false;
|
|
856
|
-
const PAUSE_THRESHOLD_MS = 2000;
|
|
857
|
-
const sendToolStatus = (phase) => {
|
|
858
|
-
try {
|
|
859
|
-
ws.send(JSON.stringify({
|
|
860
|
-
type: 'tool_status',
|
|
861
|
-
data: {
|
|
862
|
-
toolCallId: PAUSE_TOOL_ID,
|
|
863
|
-
name: 'working',
|
|
864
|
-
phase,
|
|
865
|
-
},
|
|
866
|
-
}));
|
|
867
|
-
}
|
|
868
|
-
catch {
|
|
869
|
-
/* ws closed */
|
|
870
|
-
}
|
|
871
|
-
};
|
|
872
|
-
const startPauseDetection = () => {
|
|
873
|
-
if (pauseTimer)
|
|
874
|
-
clearTimeout(pauseTimer);
|
|
875
|
-
pauseTimer = setTimeout(() => {
|
|
876
|
-
if (!toolActivitySent) {
|
|
877
|
-
toolActivitySent = true;
|
|
878
|
-
sendToolStatus('running');
|
|
879
|
-
}
|
|
880
|
-
}, PAUSE_THRESHOLD_MS);
|
|
881
|
-
};
|
|
882
|
-
startPauseDetection();
|
|
883
|
-
for await (const chunk of pluginService.executePluginStreamRequest(actualModelName, messagesForPlugin, mergedOptions)) {
|
|
884
|
-
if (chunk.type === 'content' && chunk.content) {
|
|
885
|
-
// Content arrived ā clear the tool indicator
|
|
886
|
-
if (toolActivitySent) {
|
|
887
|
-
sendToolStatus('done');
|
|
888
|
-
toolActivitySent = false;
|
|
889
|
-
}
|
|
890
|
-
startPauseDetection();
|
|
891
|
-
totalContent += chunk.content;
|
|
892
|
-
ws.send(JSON.stringify({
|
|
893
|
-
type: 'assistant_chunk',
|
|
894
|
-
data: {
|
|
895
|
-
content: chunk.content,
|
|
896
|
-
total: totalContent,
|
|
897
|
-
done: false,
|
|
898
|
-
messageId: assistantMessageId,
|
|
899
|
-
},
|
|
900
|
-
}));
|
|
901
|
-
}
|
|
902
|
-
else if (chunk.type === 'tool_call' && chunk.toolCall) {
|
|
903
|
-
toolCalls.push(chunk.toolCall);
|
|
904
|
-
if (pauseTimer)
|
|
905
|
-
clearTimeout(pauseTimer);
|
|
906
|
-
toolActivitySent = true;
|
|
907
|
-
// Send with real tool name but same stable ID
|
|
908
|
-
try {
|
|
909
|
-
ws.send(JSON.stringify({
|
|
910
|
-
type: 'tool_status',
|
|
911
|
-
data: {
|
|
912
|
-
toolCallId: PAUSE_TOOL_ID,
|
|
913
|
-
name: chunk.toolCall.name,
|
|
914
|
-
phase: 'running',
|
|
915
|
-
},
|
|
916
|
-
}));
|
|
917
|
-
}
|
|
918
|
-
catch {
|
|
919
|
-
/* ws closed */
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
else if (chunk.type === 'done') {
|
|
923
|
-
if (pauseTimer)
|
|
924
|
-
clearTimeout(pauseTimer);
|
|
925
|
-
if (toolActivitySent) {
|
|
926
|
-
sendToolStatus('done');
|
|
927
|
-
toolActivitySent = false;
|
|
928
|
-
}
|
|
929
|
-
// Append tool call info to content if present
|
|
930
|
-
if (toolCalls.length > 0) {
|
|
931
|
-
let toolContent = '\n\n---\n**š§ Tool Calls:**\n';
|
|
932
|
-
for (const tc of toolCalls) {
|
|
933
|
-
let argsFormatted = tc.arguments;
|
|
934
|
-
try {
|
|
935
|
-
argsFormatted = JSON.stringify(JSON.parse(tc.arguments), null, 2);
|
|
936
|
-
}
|
|
937
|
-
catch {
|
|
938
|
-
/* keep raw */
|
|
939
|
-
}
|
|
940
|
-
toolContent += `\n**${tc.name}** (\`${tc.id}\`)\n\`\`\`json\n${argsFormatted}\n\`\`\`\n`;
|
|
941
|
-
}
|
|
942
|
-
totalContent += toolContent;
|
|
943
|
-
}
|
|
944
|
-
// Send final chunk
|
|
945
|
-
ws.send(JSON.stringify({
|
|
946
|
-
type: 'assistant_chunk',
|
|
947
|
-
data: {
|
|
948
|
-
content: '',
|
|
949
|
-
total: totalContent,
|
|
950
|
-
done: true,
|
|
951
|
-
messageId: assistantMessageId,
|
|
952
|
-
},
|
|
953
|
-
}));
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
assistantContent = totalContent;
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
959
|
-
// Non-streaming: use original request method
|
|
960
|
-
const pluginResponse = await pluginService.executePluginRequest(actualModelName, messagesForPlugin, mergedOptions);
|
|
961
|
-
if (!pluginResponse?.choices?.length) {
|
|
962
|
-
throw new Error('Plugin returned empty or invalid response');
|
|
963
|
-
}
|
|
964
|
-
const choice = pluginResponse.choices[0];
|
|
965
|
-
// Handle content that may be a string or array of content blocks
|
|
966
|
-
const rawContent = choice?.message?.content;
|
|
967
|
-
if (Array.isArray(rawContent)) {
|
|
968
|
-
// Multimodal response: convert content blocks to markdown
|
|
969
|
-
const parts = [];
|
|
970
|
-
for (const block of rawContent) {
|
|
971
|
-
if (block.type === 'text' && block.text) {
|
|
972
|
-
parts.push(block.text);
|
|
973
|
-
}
|
|
974
|
-
else if (block.type === 'image_url' &&
|
|
975
|
-
block.image_url?.url) {
|
|
976
|
-
parts.push(``);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
assistantContent = parts.join('\n\n');
|
|
980
|
-
}
|
|
981
|
-
else {
|
|
982
|
-
assistantContent = rawContent || '';
|
|
983
|
-
}
|
|
984
|
-
// Render tool_calls from non-streaming response
|
|
985
|
-
const msgAny = choice?.message;
|
|
986
|
-
if (msgAny?.tool_calls &&
|
|
987
|
-
Array.isArray(msgAny.tool_calls) &&
|
|
988
|
-
msgAny.tool_calls.length > 0) {
|
|
989
|
-
let toolContent = '\n\n---\n**š§ Tool Calls:**\n';
|
|
990
|
-
for (const tc of msgAny.tool_calls) {
|
|
991
|
-
const name = tc.function?.name || 'unknown';
|
|
992
|
-
const id = tc.id || '';
|
|
993
|
-
let args = tc.function?.arguments || '';
|
|
994
|
-
try {
|
|
995
|
-
args = JSON.stringify(JSON.parse(args), null, 2);
|
|
996
|
-
}
|
|
997
|
-
catch {
|
|
998
|
-
/* keep raw */
|
|
999
|
-
}
|
|
1000
|
-
toolContent += `\n**${name}** (\`${id}\`)\n\`\`\`json\n${args}\n\`\`\`\n`;
|
|
1001
|
-
}
|
|
1002
|
-
assistantContent += toolContent;
|
|
1003
|
-
}
|
|
1004
|
-
// Send the complete response as chunks to simulate streaming
|
|
1005
|
-
const words = assistantContent.split(' ');
|
|
1006
|
-
const BATCH_SIZE = 3;
|
|
1007
|
-
for (let i = 0; i < words.length; i += BATCH_SIZE) {
|
|
1008
|
-
const batch = words.slice(i, i + BATCH_SIZE);
|
|
1009
|
-
const chunk = words.slice(0, i + batch.length).join(' ');
|
|
1010
|
-
const isLast = i + BATCH_SIZE >= words.length;
|
|
1011
|
-
ws.send(JSON.stringify({
|
|
1012
|
-
type: 'assistant_chunk',
|
|
1013
|
-
data: {
|
|
1014
|
-
content: batch.join(' ') + (isLast ? '' : ' '),
|
|
1015
|
-
total: chunk,
|
|
1016
|
-
done: isLast,
|
|
1017
|
-
messageId: assistantMessageId,
|
|
1018
|
-
},
|
|
1019
|
-
}));
|
|
1020
|
-
if (!isLast) {
|
|
1021
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
// Save the complete assistant message (skip for private sessions)
|
|
1026
|
-
if (assistantContent && assistantMessageId) {
|
|
1027
|
-
if (isPrivate) {
|
|
1028
|
-
// For private sessions, just send completion without saving
|
|
1029
|
-
console.log('Backend: Private session - skipping message save');
|
|
1030
|
-
ws.send(JSON.stringify({
|
|
1031
|
-
type: 'assistant_complete',
|
|
1032
|
-
data: {
|
|
1033
|
-
id: assistantMessageId,
|
|
1034
|
-
role: 'assistant',
|
|
1035
|
-
content: assistantContent,
|
|
1036
|
-
model: session.model,
|
|
1037
|
-
timestamp: Date.now(),
|
|
1038
|
-
},
|
|
1039
|
-
}));
|
|
1040
|
-
}
|
|
1041
|
-
else {
|
|
1042
|
-
console.log('Backend: Saving complete assistant message with ID:', assistantMessageId, 'regenerate:', !!regenerate);
|
|
1043
|
-
// Calculate branching fields if this is a regeneration
|
|
1044
|
-
let branchingFields = {};
|
|
1045
|
-
if (regenerate && originalMessageId) {
|
|
1046
|
-
// Find the original message to get its parentId or use its ID as parent
|
|
1047
|
-
const originalMsg = session.messages.find(m => m.id === originalMessageId);
|
|
1048
|
-
const parentId = originalMsg?.parentId || originalMessageId;
|
|
1049
|
-
// Count existing siblings to determine branch index
|
|
1050
|
-
const siblingCount = session.messages.filter(m => m.id === parentId || m.parentId === parentId).length;
|
|
1051
|
-
branchingFields = {
|
|
1052
|
-
parentId,
|
|
1053
|
-
branchIndex: siblingCount, // New branch gets next index
|
|
1054
|
-
isActive: true,
|
|
1055
|
-
};
|
|
1056
|
-
console.log('Backend: Setting branching fields:', branchingFields);
|
|
1057
|
-
}
|
|
1058
|
-
const assistantMessage = chatService.addMessage(sessionId, {
|
|
1059
|
-
role: 'assistant',
|
|
1060
|
-
content: assistantContent,
|
|
1061
|
-
model: session.model,
|
|
1062
|
-
id: assistantMessageId,
|
|
1063
|
-
...branchingFields,
|
|
1064
|
-
}, userId);
|
|
1065
|
-
console.log('Backend: Assistant message saved:', !!assistantMessage);
|
|
1066
|
-
// Send completion signal
|
|
1067
|
-
ws.send(JSON.stringify({
|
|
1068
|
-
type: 'assistant_complete',
|
|
1069
|
-
data: assistantMessage,
|
|
1070
|
-
}));
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
return; // Exit early since we handled the request via plugin
|
|
1074
|
-
}
|
|
1075
|
-
catch (pluginError) {
|
|
1076
|
-
const err = pluginError instanceof Error
|
|
1077
|
-
? pluginError
|
|
1078
|
-
: new Error(String(pluginError));
|
|
1079
|
-
const errWithResponse = pluginError;
|
|
1080
|
-
console.error('Plugin failed, falling back to Ollama:', err.message);
|
|
1081
|
-
if (errWithResponse &&
|
|
1082
|
-
typeof errWithResponse === 'object' &&
|
|
1083
|
-
'response' in errWithResponse) {
|
|
1084
|
-
const resp = errWithResponse.response;
|
|
1085
|
-
console.error('Plugin HTTP response status:', resp.status);
|
|
1086
|
-
console.error('Plugin HTTP response data:', JSON.stringify(resp.data));
|
|
1087
|
-
}
|
|
1088
|
-
if ('cause' in err) {
|
|
1089
|
-
console.error('Plugin error cause:', err.cause);
|
|
1090
|
-
}
|
|
1091
|
-
// If a plugin was found but failed, don't fall through to Ollama
|
|
1092
|
-
if (activePlugin) {
|
|
1093
|
-
console.error(`[WebSocket] Plugin "${activePlugin.name}" failed for model ${actualModelName}, not falling back to Ollama`);
|
|
1094
|
-
ws.send(JSON.stringify({
|
|
1095
|
-
type: 'error',
|
|
1096
|
-
message: `Plugin request failed: ${err.message}`,
|
|
1097
|
-
}));
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
// Continue to Ollama fallback below (no plugin was matched)
|
|
1101
|
-
}
|
|
1102
|
-
} // close else (standard plugin path)
|
|
1103
|
-
}
|
|
1104
|
-
console.log(`[WebSocket] No plugin found, using Ollama for model: ${actualModelName}`);
|
|
1105
|
-
// Reuse the actualModelName variable that was already resolved above
|
|
1106
|
-
// If we're here, it means either there was no plugin or plugin failed
|
|
1107
|
-
// The actualModelName was already resolved in the earlier code block
|
|
1108
|
-
// Get user's preferred generation options
|
|
1109
|
-
const userGenerationOptions = preferencesService.getGenerationOptions();
|
|
1110
|
-
// Merge user preferences with request options
|
|
1111
|
-
const mergedOptions = mergeGenerationOptions(userGenerationOptions, options);
|
|
1112
|
-
// Create chat request with advanced features
|
|
1113
|
-
const chatRequest = {
|
|
1114
|
-
model: actualModelName,
|
|
1115
|
-
messages: ollamaMessages,
|
|
1116
|
-
stream: true,
|
|
1117
|
-
options: mergedOptions,
|
|
1118
|
-
};
|
|
1119
|
-
// Add structured output format if specified
|
|
1120
|
-
if (format) {
|
|
1121
|
-
chatRequest.format = format;
|
|
1122
|
-
}
|
|
1123
|
-
// Stream response from Ollama using chat completion
|
|
1124
|
-
await ollamaService.generateChatStreamResponse(chatRequest, chunk => {
|
|
1125
|
-
if (chunk.message?.content) {
|
|
1126
|
-
assistantContent += chunk.message.content;
|
|
1127
|
-
// Send streaming chunk with the provided message ID
|
|
1128
|
-
ws.send(JSON.stringify({
|
|
1129
|
-
type: 'assistant_chunk',
|
|
1130
|
-
data: {
|
|
1131
|
-
content: chunk.message.content,
|
|
1132
|
-
total: assistantContent,
|
|
1133
|
-
done: chunk.done,
|
|
1134
|
-
messageId: assistantMessageId,
|
|
1135
|
-
},
|
|
1136
|
-
}));
|
|
1137
|
-
}
|
|
1138
|
-
// Capture final statistics when streaming is done
|
|
1139
|
-
if (chunk.done) {
|
|
1140
|
-
finalStatistics = {
|
|
1141
|
-
total_duration: chunk.total_duration,
|
|
1142
|
-
load_duration: chunk.load_duration,
|
|
1143
|
-
prompt_eval_count: chunk.prompt_eval_count,
|
|
1144
|
-
prompt_eval_duration: chunk.prompt_eval_duration,
|
|
1145
|
-
eval_count: chunk.eval_count,
|
|
1146
|
-
eval_duration: chunk.eval_duration,
|
|
1147
|
-
created_at: chunk.created_at,
|
|
1148
|
-
model: chunk.model,
|
|
1149
|
-
};
|
|
1150
|
-
// Calculate tokens per second if we have the necessary data
|
|
1151
|
-
if (chunk.eval_count && chunk.eval_duration) {
|
|
1152
|
-
finalStatistics.tokens_per_second =
|
|
1153
|
-
Math.round((chunk.eval_count / (chunk.eval_duration / 1e9)) * 100) / 100;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}, error => {
|
|
1157
|
-
ws.send(JSON.stringify({
|
|
1158
|
-
type: 'error',
|
|
1159
|
-
data: { error: error.message },
|
|
1160
|
-
}));
|
|
1161
|
-
}, () => {
|
|
1162
|
-
// Save the complete assistant message with the provided ID (skip for private sessions)
|
|
1163
|
-
if (assistantContent && assistantMessageId) {
|
|
1164
|
-
if (isPrivate) {
|
|
1165
|
-
// For private sessions, just send completion without saving
|
|
1166
|
-
console.log('Backend: Private session - skipping Ollama message save');
|
|
1167
|
-
ws.send(JSON.stringify({
|
|
1168
|
-
type: 'assistant_complete',
|
|
1169
|
-
data: {
|
|
1170
|
-
content: assistantContent,
|
|
1171
|
-
role: 'assistant',
|
|
1172
|
-
timestamp: Date.now(),
|
|
1173
|
-
messageId: assistantMessageId,
|
|
1174
|
-
statistics: finalStatistics,
|
|
1175
|
-
},
|
|
1176
|
-
}));
|
|
1177
|
-
}
|
|
1178
|
-
else {
|
|
1179
|
-
console.log('Backend: Saving complete assistant message with ID:', assistantMessageId, 'regenerate:', !!regenerate);
|
|
1180
|
-
// Calculate branching fields if this is a regeneration
|
|
1181
|
-
let branchingFields = {};
|
|
1182
|
-
if (regenerate && originalMessageId) {
|
|
1183
|
-
// Find the original message to get its parentId or use its ID as parent
|
|
1184
|
-
const originalMsg = session.messages.find(m => m.id === originalMessageId);
|
|
1185
|
-
const parentId = originalMsg?.parentId || originalMessageId;
|
|
1186
|
-
// Count existing siblings to determine branch index
|
|
1187
|
-
const siblingCount = session.messages.filter(m => m.id === parentId || m.parentId === parentId).length;
|
|
1188
|
-
branchingFields = {
|
|
1189
|
-
parentId,
|
|
1190
|
-
branchIndex: siblingCount, // New branch gets next index
|
|
1191
|
-
isActive: true,
|
|
1192
|
-
};
|
|
1193
|
-
console.log('Backend: Setting branching fields:', branchingFields);
|
|
1194
|
-
}
|
|
1195
|
-
console.log('Backend: About to save assistant message:', {
|
|
1196
|
-
sessionId,
|
|
1197
|
-
messageId: assistantMessageId,
|
|
1198
|
-
contentLength: assistantContent.length,
|
|
1199
|
-
hasBranchingFields: Object.keys(branchingFields).length > 0,
|
|
1200
|
-
branchingFields,
|
|
1201
|
-
});
|
|
1202
|
-
const assistantMessage = chatService.addMessage(sessionId, {
|
|
1203
|
-
role: 'assistant',
|
|
1204
|
-
content: assistantContent,
|
|
1205
|
-
model: session.model,
|
|
1206
|
-
id: assistantMessageId,
|
|
1207
|
-
statistics: finalStatistics,
|
|
1208
|
-
...branchingFields,
|
|
1209
|
-
}, userId);
|
|
1210
|
-
console.log('Backend: Assistant message saved:', !!assistantMessage, assistantMessage
|
|
1211
|
-
? {
|
|
1212
|
-
id: assistantMessage.id,
|
|
1213
|
-
contentLength: assistantMessage.content.length,
|
|
1214
|
-
}
|
|
1215
|
-
: 'FAILED TO SAVE');
|
|
1216
|
-
// Send completion signal with statistics
|
|
1217
|
-
ws.send(JSON.stringify({
|
|
1218
|
-
type: 'assistant_complete',
|
|
1219
|
-
data: {
|
|
1220
|
-
content: assistantContent,
|
|
1221
|
-
role: 'assistant',
|
|
1222
|
-
timestamp: Date.now(),
|
|
1223
|
-
messageId: assistantMessageId,
|
|
1224
|
-
statistics: finalStatistics,
|
|
1225
|
-
...branchingFields,
|
|
1226
|
-
},
|
|
1227
|
-
}));
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
catch (error) {
|
|
1234
|
-
console.error('WebSocket error:', error);
|
|
1235
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1236
|
-
ws.send(JSON.stringify({
|
|
1237
|
-
type: 'error',
|
|
1238
|
-
data: { error: errorMessage },
|
|
1239
|
-
}));
|
|
1240
|
-
}
|
|
1241
|
-
});
|
|
1242
|
-
ws.on('close', () => {
|
|
1243
|
-
console.log('WebSocket client disconnected');
|
|
1244
|
-
});
|
|
1245
|
-
ws.on('error', error => {
|
|
1246
|
-
console.error('WebSocket error:', error);
|
|
1247
|
-
});
|
|
1248
|
-
// Send initial connection confirmation
|
|
1249
|
-
ws.send(JSON.stringify({
|
|
1250
|
-
type: 'connected',
|
|
1251
|
-
data: { message: 'Connected to Libre WebUI' },
|
|
1252
|
-
}));
|
|
1253
|
-
});
|
|
384
|
+
registerWebSocketServer(server);
|
|
1254
385
|
// Start server
|
|
1255
386
|
server.listen({ port, host: '0.0.0.0' }, () => {
|
|
1256
387
|
const url = `http://localhost:${port}`;
|
|
1257
|
-
|
|
1258
|
-
|
|
388
|
+
logger.info(`Libre WebUI v${pkg.version}`);
|
|
389
|
+
logger.info(url);
|
|
1259
390
|
// Open browser in production mode
|
|
1260
391
|
if (process.env.SERVE_FRONTEND === 'true') {
|
|
1261
392
|
import('child_process').then(({ exec }) => {
|
|
@@ -1273,38 +404,38 @@ server.listen({ port, host: '0.0.0.0' }, () => {
|
|
|
1273
404
|
const githubConfigured = githubOAuth.isConfigured();
|
|
1274
405
|
const hfConfigured = hfOAuth.isConfigured();
|
|
1275
406
|
if (githubConfigured || hfConfigured) {
|
|
1276
|
-
|
|
407
|
+
logger.info('SSO configuration:');
|
|
1277
408
|
if (githubConfigured) {
|
|
1278
|
-
|
|
409
|
+
logger.info('GitHub OAuth configured and ready');
|
|
1279
410
|
}
|
|
1280
411
|
if (hfConfigured) {
|
|
1281
|
-
|
|
412
|
+
logger.info('Hugging Face OAuth configured and ready');
|
|
1282
413
|
}
|
|
1283
414
|
}
|
|
1284
415
|
else {
|
|
1285
|
-
|
|
416
|
+
logger.info('No SSO providers configured (optional)');
|
|
1286
417
|
}
|
|
1287
418
|
// Check Ollama connection on startup
|
|
1288
419
|
ollamaService.isHealthy().then(isHealthy => {
|
|
1289
420
|
if (isHealthy) {
|
|
1290
|
-
|
|
421
|
+
logger.info('Ollama service is connected and ready');
|
|
1291
422
|
}
|
|
1292
423
|
else {
|
|
1293
|
-
|
|
424
|
+
logger.warn("Ollama service is not available - make sure it's running on http://localhost:11434");
|
|
1294
425
|
}
|
|
1295
426
|
});
|
|
1296
427
|
});
|
|
1297
428
|
// Graceful shutdown
|
|
1298
429
|
process.on('SIGTERM', () => {
|
|
1299
|
-
|
|
430
|
+
logger.info('SIGTERM signal received: closing HTTP server');
|
|
1300
431
|
server.close(() => {
|
|
1301
|
-
|
|
432
|
+
logger.info('HTTP server closed');
|
|
1302
433
|
});
|
|
1303
434
|
});
|
|
1304
435
|
process.on('SIGINT', () => {
|
|
1305
|
-
|
|
436
|
+
logger.info('SIGINT signal received: closing HTTP server');
|
|
1306
437
|
server.close(() => {
|
|
1307
|
-
|
|
438
|
+
logger.info('HTTP server closed');
|
|
1308
439
|
});
|
|
1309
440
|
});
|
|
1310
441
|
export default app;
|