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