libre-webui 0.10.0 ā 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 +26 -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/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/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 +120 -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/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 +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 +2 -2
- 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 +11 -10
- 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-Dh6nfvM2.js ā react-vendor-DtQrJICq.js} +1 -1
- 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 +12 -8
- 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-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/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';
|
|
@@ -54,18 +53,14 @@ import imageGenRoutes from './routes/imageGen.js';
|
|
|
54
53
|
import embeddingsRoutes from './routes/embeddings.js';
|
|
55
54
|
import huggingfaceHubRoutes from './routes/huggingfaceHub.js';
|
|
56
55
|
import ollamaService from './services/ollamaService.js';
|
|
57
|
-
import chatService from './services/chatService.js';
|
|
58
56
|
import { GitHubOAuthService } from './services/simpleGitHubOAuth.js';
|
|
59
57
|
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
58
|
import { loadAppPackage, resolveFrontendDist } from './utils/packagePaths.js';
|
|
59
|
+
import { registerWebSocketServer } from './websocketServer.js';
|
|
60
|
+
import { createLogger } from './utils/logger.js';
|
|
67
61
|
const pkg = loadAppPackage(import.meta.url);
|
|
68
62
|
const app = express();
|
|
63
|
+
const logger = createLogger('server');
|
|
69
64
|
// Trust proxy setting for running behind reverse proxies (Nginx, Caddy, etc.)
|
|
70
65
|
// Set TRUST_PROXY=1 or TRUST_PROXY=loopback or TRUST_PROXY=uniquelocal etc.
|
|
71
66
|
const trustProxy = process.env.TRUST_PROXY;
|
|
@@ -83,6 +78,8 @@ if (trustProxy) {
|
|
|
83
78
|
}
|
|
84
79
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
85
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());
|
|
86
83
|
const corsOrigins = process.env.CORS_ORIGIN?.split(',') || [
|
|
87
84
|
'http://localhost:5173',
|
|
88
85
|
'http://localhost:3000',
|
|
@@ -109,7 +106,7 @@ const corsConfig = {
|
|
|
109
106
|
const allowNetworkAccess = process.env.NODE_ENV !== 'production' ||
|
|
110
107
|
process.env.DOCKER_ENV === 'true';
|
|
111
108
|
const isNetworkOrigin = origin &&
|
|
112
|
-
/^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);
|
|
113
110
|
if (allowNetworkAccess && isNetworkOrigin) {
|
|
114
111
|
callback(null, true);
|
|
115
112
|
}
|
|
@@ -122,7 +119,9 @@ const corsConfig = {
|
|
|
122
119
|
// Security middleware
|
|
123
120
|
app.use(helmet({
|
|
124
121
|
// COEP - disable in Docker/development to avoid proxy issues
|
|
125
|
-
crossOriginEmbedderPolicy: process.env.NODE_ENV === 'production' &&
|
|
122
|
+
crossOriginEmbedderPolicy: process.env.NODE_ENV === 'production' &&
|
|
123
|
+
!process.env.DOCKER_ENV &&
|
|
124
|
+
!hasTurnstile
|
|
126
125
|
? true
|
|
127
126
|
: false,
|
|
128
127
|
// Content Security Policy - Docker-aware configuration
|
|
@@ -131,6 +130,7 @@ app.use(helmet({
|
|
|
131
130
|
defaultSrc: ["'self'"],
|
|
132
131
|
scriptSrc: [
|
|
133
132
|
"'self'",
|
|
133
|
+
'https://challenges.cloudflare.com',
|
|
134
134
|
...(process.env.NODE_ENV === 'production'
|
|
135
135
|
? [] // Strict in production
|
|
136
136
|
: ["'unsafe-inline'", "'unsafe-eval'"]), // Allow for dev tools
|
|
@@ -164,6 +164,7 @@ app.use(helmet({
|
|
|
164
164
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
|
|
165
165
|
mediaSrc: ["'self'", 'data:', 'blob:'],
|
|
166
166
|
objectSrc: ["'none'"],
|
|
167
|
+
frameSrc: ["'self'", 'https://challenges.cloudflare.com'],
|
|
167
168
|
frameAncestors: ["'self'"],
|
|
168
169
|
formAction: ["'self'"],
|
|
169
170
|
upgradeInsecureRequests: process.env.NODE_ENV === 'production' && !process.env.DOCKER_ENV
|
|
@@ -334,7 +335,7 @@ if (process.env.NODE_ENV === 'production' ||
|
|
|
334
335
|
const pathModule = await import('path');
|
|
335
336
|
const frontendPath = resolveFrontendDist(import.meta.url);
|
|
336
337
|
if (frontendPath) {
|
|
337
|
-
|
|
338
|
+
logger.info(`Serving frontend from: ${frontendPath}`);
|
|
338
339
|
// Rate limiter for static files
|
|
339
340
|
const staticRateLimiter = rateLimit({
|
|
340
341
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
@@ -359,7 +360,7 @@ if (process.env.NODE_ENV === 'production' ||
|
|
|
359
360
|
});
|
|
360
361
|
}
|
|
361
362
|
else {
|
|
362
|
-
|
|
363
|
+
logger.warn('Frontend build not found. Run `npm run build:frontend` first.');
|
|
363
364
|
}
|
|
364
365
|
}
|
|
365
366
|
// Error handling
|
|
@@ -367,895 +368,12 @@ app.use(notFoundHandler);
|
|
|
367
368
|
app.use(errorHandler);
|
|
368
369
|
// Create HTTP server
|
|
369
370
|
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
|
-
});
|
|
371
|
+
registerWebSocketServer(server);
|
|
1254
372
|
// Start server
|
|
1255
373
|
server.listen({ port, host: '0.0.0.0' }, () => {
|
|
1256
374
|
const url = `http://localhost:${port}`;
|
|
1257
|
-
|
|
1258
|
-
|
|
375
|
+
logger.info(`Libre WebUI v${pkg.version}`);
|
|
376
|
+
logger.info(url);
|
|
1259
377
|
// Open browser in production mode
|
|
1260
378
|
if (process.env.SERVE_FRONTEND === 'true') {
|
|
1261
379
|
import('child_process').then(({ exec }) => {
|
|
@@ -1273,38 +391,38 @@ server.listen({ port, host: '0.0.0.0' }, () => {
|
|
|
1273
391
|
const githubConfigured = githubOAuth.isConfigured();
|
|
1274
392
|
const hfConfigured = hfOAuth.isConfigured();
|
|
1275
393
|
if (githubConfigured || hfConfigured) {
|
|
1276
|
-
|
|
394
|
+
logger.info('SSO configuration:');
|
|
1277
395
|
if (githubConfigured) {
|
|
1278
|
-
|
|
396
|
+
logger.info('GitHub OAuth configured and ready');
|
|
1279
397
|
}
|
|
1280
398
|
if (hfConfigured) {
|
|
1281
|
-
|
|
399
|
+
logger.info('Hugging Face OAuth configured and ready');
|
|
1282
400
|
}
|
|
1283
401
|
}
|
|
1284
402
|
else {
|
|
1285
|
-
|
|
403
|
+
logger.info('No SSO providers configured (optional)');
|
|
1286
404
|
}
|
|
1287
405
|
// Check Ollama connection on startup
|
|
1288
406
|
ollamaService.isHealthy().then(isHealthy => {
|
|
1289
407
|
if (isHealthy) {
|
|
1290
|
-
|
|
408
|
+
logger.info('Ollama service is connected and ready');
|
|
1291
409
|
}
|
|
1292
410
|
else {
|
|
1293
|
-
|
|
411
|
+
logger.warn("Ollama service is not available - make sure it's running on http://localhost:11434");
|
|
1294
412
|
}
|
|
1295
413
|
});
|
|
1296
414
|
});
|
|
1297
415
|
// Graceful shutdown
|
|
1298
416
|
process.on('SIGTERM', () => {
|
|
1299
|
-
|
|
417
|
+
logger.info('SIGTERM signal received: closing HTTP server');
|
|
1300
418
|
server.close(() => {
|
|
1301
|
-
|
|
419
|
+
logger.info('HTTP server closed');
|
|
1302
420
|
});
|
|
1303
421
|
});
|
|
1304
422
|
process.on('SIGINT', () => {
|
|
1305
|
-
|
|
423
|
+
logger.info('SIGINT signal received: closing HTTP server');
|
|
1306
424
|
server.close(() => {
|
|
1307
|
-
|
|
425
|
+
logger.info('HTTP server closed');
|
|
1308
426
|
});
|
|
1309
427
|
});
|
|
1310
428
|
export default app;
|